使用Castle扩展Ibatis.Net,面向接口编程-更优雅的代码
使用Ibatis.Net做项目半年了,甚是喜欢,感觉确实是个简单、轻巧的O/R Mapping框架,特别是将Sql配置在Xml文件中,相当于直接将Dao层抽离了出来。
本文假定读者对Ibatis.Net有一定的了解。
最近试用了一下Ibatis.Net的亲兄弟--Java的Mybatis,一对比发现:
执行一个查询,Ibatis.Net是这么写的:IList<UserEntity> list = SqlMapper.QueryForList<UserEntity>(prefix+ ".GetByFilter", parameters);
而Java的Mybatis是这么写的:List<UserEntity> list = dao.GetByFilter(parameters);
发现了没,后者的显然更优雅。
Mybatis之所以能这么调用,是因为Mybatis提供了一种面向接口编程的方法,只要写好接口,接口的方法名与map文件中sql片段的id相同,我们就能够直接通过接口调用。
我想了又想...C#也能够实现这样优雅的调用啊,可是为啥Ibatis.Net不提供呢,想到这,我开始明白Ibatis.Net是后妈生的。。。
说到这,进入主题吧,既然Ibatis.Net先天不够强大,那我们后天弥补吧,这里主要使用Castle这个组件来动态实现接口。
接下来我们做个Demo
1.搭建Ibatis.Net环境,这里就不说啦(最新版Ibatis.Net下载地址:http://download.csdn.net/detail/tzjzcy/7829759 )
2.引用Castle.Core.dll,这个dll实际上最新版的Ibatis.Net本身就有用到
3.创建一个测试表,录入数据,本文以mysql为例,代码如下:
1 CREATE TABLE `user`( 2 `Userid` INT NOT NULL AUTO_INCREMENT, 3 `Username` VARCHAR(100), 4 `Age` INT, 5 `City` VARCHAR(100), PRIMARY KEY (`Userid`) 6 ); 7 8 INSERT INTO `testex`.`user` (`Username`, `Age`, `City`) VALUES ('羊望', '26', '厦门'); 9 INSERT INTO `testex`.`user` (`Userid`, `Username`, `Age`, `City`) VALUES ('2', '测试', '18', '福州');
4.编写对应实体类
1 public class UserEntity 2 { 3 public int? Userid { get; set; } 4 5 public string Username { get; set; } 6 7 public int? Age { get; set; } 8 9 public string City { get; set; } 10 }
5.写个简单的map文件:UserMap.xml
1 <?xml version="1.0" encoding="utf-8" ?> 2 <!--这里的namespace必须对应Dao接口的完整类名--> 3 <sqlMap namespace="IbatisExTest.Daos.IUserDao" 4 xmlns="http://ibatis.apache.org/mapping" 5 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 6 <alias> 7 <typeAlias alias="UserEntity" type="IbatisExTest.Entities.UserEntity,IbatisExTest" /> 8 </alias> 9 10 <statements> 11 12 <select id="GetByUserid" parameterClass="String" resultClass="UserEntity"> 13 SELECT * 14 FROM user 15 <dynamic prepend="WHERE"> 16 Userid =#value# 17 </dynamic> 18 </select> 19 20 <select id="GetByFilter" parameterClass="Hashtable" resultClass="UserEntity"> 21 SELECT * 22 From user 23 <dynamic prepend="WHERE"> 24 <isNotEmpty prepend="AND" property="Userid"> 25 Userid =#Userid# 26 </isNotEmpty> 27 <isNotEmpty prepend="AND" property="Username"> 28 Username =#Username# 29 </isNotEmpty> 30 </dynamic> 31 </select> 32 33 <insert id="InsertUser" parameterClass="UserEntity"> 34 INSERT INTO user 35 ( Username 36 , Age 37 , City) 38 VALUES ( 39 #Username# 40 , #Age# 41 , #City#); 42 </insert> 43 44 </statements> 45 </sqlMap>
6.写一个接口,接口的全名(命名空间+接口名)必须与map文件的namespace相同,接口的方法与map文件中的sql片段id对应
1 public interface IUserDao 2 { 3 4 UserEntity GetByUserid(string userid); 5 6 IList<UserEntity> GetByFilter(Hashtable ht); 7 8 object InsertUser(UserEntity user); 9 }
7.写一个BaseDao,作为动态创建的Dao实现类的基类,定义一个属性,传入SqlMapper用
public class BaseDao { public BaseDao(ISqlMapper sqlMapper) { this.SqlMapper = sqlMapper; } public ISqlMapper SqlMapper { get; private set; } }
8.重点来了,编写Dao实现类的具体方法实现,通过Castle组件实现的,作用是:在调用接口的方法时,执行map中对应的sql片段
1 /// <summary> 2 /// Dao接口的方法实现 3 /// </summary> 4 public class DaoInterceptor : IInterceptor 5 { 6 public void Intercept(IInvocation invocation) 7 { 8 BaseDao baseDao = (BaseDao)invocation.Proxy; 9 //从基类BaseDao获取sqlMapper实例 10 ISqlMapper sqlMapper = baseDao.SqlMapper; 11 MethodInfo method = invocation.Method; 12 if (method.DeclaringType == null) return; 13 //获取接口的全名,即map文件的Namespace 14 string mapNamespace = method.DeclaringType.FullName; 15 //得到要执行的sql的完整id 16 string statementId = mapNamespace + "." + method.Name; 17 IMappedStatement ms = sqlMapper.GetMappedStatement(statementId); 18 if (ms is SelectMappedStatement) 19 { 20 ProcessSelectStatement(invocation, sqlMapper, statementId); 21 } 22 else if (ms is InsertMappedStatement) 23 { 24 ProcessInsertStatement(invocation, sqlMapper, statementId); 25 } 26 else if (ms is UpdateMappedStatement) 27 { 28 ProcessUpdateStatement(invocation, sqlMapper, statementId); 29 } 30 else if (ms is DeleteMappedStatement) 31 { 32 ProcessDeleteStatement(invocation, sqlMapper, statementId); 33 } 34 } 35 36 private static void ProcessSelectStatement(IInvocation invocation, ISqlMapper sqlMapper, string statementId) 37 { 38 MethodInfo method = invocation.Method; 39 if (method.ReturnType.IsGenericType) //判断方法的返回值,如果是泛型,表示返回值是泛型集合 40 { 41 //通过反射调用sqlMapper.QueryForList方法 42 Type t = typeof(List<>).MakeGenericType(method.ReturnType.GetGenericArguments()); 43 var list = Activator.CreateInstance(t); 44 MethodInfo miQueryForList = typeof(ISqlMapper).GetMethod("QueryForList", 45 new Type[] { typeof(string), typeof(object), typeof(List<>) }); 46 miQueryForList.Invoke(sqlMapper, new object[] { statementId, invocation.Arguments[0], list }); 47 invocation.ReturnValue = list; 48 } 49 else //返回单个对象,或int等基本类型 50 { 51 //直接调用sqlMapper.QueryForObject方法 52 invocation.ReturnValue = sqlMapper.QueryForObject(statementId, invocation.Arguments[0]); 53 } 54 } 55 56 private static void ProcessInsertStatement(IInvocation invocation, ISqlMapper sqlMapper, string statementId) 57 { 58 invocation.ReturnValue = sqlMapper.Insert(statementId, invocation.Arguments[0]); 59 } 60 61 private static void ProcessUpdateStatement(IInvocation invocation, ISqlMapper sqlMapper, string statementId) 62 { 63 invocation.ReturnValue = sqlMapper.Update(statementId, invocation.Arguments[0]); 64 } 65 66 private static void ProcessDeleteStatement(IInvocation invocation, ISqlMapper sqlMapper, string statementId) 67 { 68 invocation.ReturnValue = sqlMapper.Delete(statementId, invocation.Arguments[0]); 69 }
9.编写SqlMapper的扩展方法,动态实现Dao接口
1 public static class SqlMapExtensionMethods 2 { 3 /// <summary> 4 /// 获取Dao的实现 5 /// </summary> 6 /// <typeparam name="T">Dao接口</typeparam> 7 /// <param name="sqlMapper">sqlMapper</param> 8 /// <returns>返回Dao的实现</returns> 9 public static T GetDao<T>(this ISqlMapper sqlMapper) 10 { 11 ProxyGenerator generator = new ProxyGenerator(); 12 DaoInterceptor daoInterceptor = new DaoInterceptor(); 13 //创建一个BaseDao的代理类,并实现指定Dao接口 14 object proxy = generator.CreateClassProxy(typeof(BaseDao), new Type[] { typeof(T) }, ProxyGenerationOptions.Default, new object[] { sqlMapper }, daoInterceptor); 15 return (T)proxy; 16 } 17 }
10.这样就完成了扩展,让我们看看调用实例吧
class Program { private const string mapperNamespace = "IbatisExTest.Daos.IUserDao"; private static ISqlMapper SqlMapper { get { return Mapper.Get(); } } private static IUserDao UserDao { get { return SqlMapper.GetDao<IUserDao>(); } } static void Main() { Hashtable ht = new Hashtable(); ht["Username"] = "羊望"; //传统用法 IList<UserEntity> list1 = SqlMapper.QueryForList<UserEntity>(mapperNamespace + ".GetByFilter", ht); //新用法(代码更优雅了吧) IList<UserEntity> list2 = UserDao.GetByFilter(ht); //测试新增 //UserEntity user = new UserEntity { Username = "新用户", Age = 11, City = "新城市" }; //UserDao.InsertUser(user); } }
最后,我们看到,扩展后我们只需要调用Dao接口的方法,代码更简洁了。
至于要比传统用法多写个Dao接口,这个工作或许我们可以通过代码生成工具来做吧。
源代码下载:http://files.cnblogs.com/lookup/Castle%E6%89%A9%E5%B1%95IbatisNet%E4%BE%8B%E5%AD%90.zip
欢迎拍砖:)