上午写了一个简单的 从xml读取信息实例化一个Bean对象。下午就开始想mybatis是怎么通过xml文件来实现dao层接口的,一开始想直接用Class.forName(String name)然后调用getMethods的办法来得到Dao接口所有的方法Method,结果证明这对接口实没有用的,会报一个错误。于是想百度,但是百度的结果全是怎么配置mapper的。。然后我又四处翻资料,终于找到一些办法。最后我还是用到了我自己封装的DButil 和 DataUtil两个类。
反正我是这么实现的,至于Mybatis是怎么实现的,我还没看源码。
有不了解的可以点下面链接
下面是我的目录结构
因为要解析xml文件,我这里引入了一个dom4j的jar包
从根源开始,下面先贴上User类和IUserdao接口
package com.yck.bean; public class User { private Integer id; private String name; private Integer age; //getter和setter方法均省略 } package com.yck.dao; import com.yck.bean.User; public interface IUserDao { User selectById(Integer id); }
然后是我准备好的userdao的xml文件
<?xml version="1.0" encoding="UTF-8"?> <dao id="com.yck.dao.IUserDao"> <select id="selectById" resultType ="com.yck.bean.User"> select * from t_user where id = ? </select> </dao>
为了习惯,我没怎么改动这个配置文件的东西,比较符合mybatis的风格
既然我们的目的是用xml配置文件来实现dao层接口。那我们第一件事就是读取xml文件的信息。
我写了两个类来存取我需要的东西 getter和setter方法我全部省略不贴了,太难看
package com.yck.bean; import java.util.List; /** * 用来存储一个dao接口的信息 * @author Administrator * */ public class MapperBean { private String interfaceName; //接口名 private List<Function> list; //接口下所有方法 } package com.yck.bean; /* * 用来存储dao接口一条方法的信息 */ public class Function { private String sqltype; //sql的类型 其实用我封装的DataUtil是只有两种类型的,但是我计划在xml读取仍有四种情况 private String funcName; // 方法名 private String sql; //执行的sql语句 private String resultType; // 返回类型 private String parameterType; //参数类型 }
然后是读取xml文件和通过反射实现dao接口所有函数的代码
public class DaoProxy { /** * 通过读取配置文件实现dao接口 * @param path * @return */ public static Object implDao(String path) { MapperBean mapper = readMapper(path); ClassLoader classLoader = DaoProxy.class.getClassLoader(); Class interfaze = null; try { interfaze = classLoader.loadClass(mapper.getInterfaceName()); //加载一个接口类 } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } /** * 下面这几句相当重要了,是用xml文件实现dao接口的核心代码,因为数据库相关的大量工作我之前都写过了,所以这个看起来代码量很少 * 我也不太懂下面这个东西,我查API查了相当久,一开始写总是错,因为我理解错了InvocationHandler接口下那个方法的Object数组参数 * 它应该理解为一个可变长数组,而不是必须为数组 */ Object instanze = Proxy.newProxyInstance(classLoader, new Class[]{interfaze}, new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { List<Function> list = mapper.getList(); Object obj = null; for(Function f:list) { if(f.getFuncName().equals(method.getName())) { /** * 判断是不是select语句,是则调用DateUtil的select方法 * 否则调用update的方法 */ if(f.getSqltype().equals("select")) obj = DataUtil.selectForBean(Class.forName(f.getResultType()), f.getSql(), args); else obj = DataUtil.updata(f.getSql(), args); } } return obj; } }); return instanze; //返回这个接口,即mapper.getInterfaceName()这个接口 } /** * 读取xml文件的信息 * @param path * @return */ @SuppressWarnings("rawtypes") private static MapperBean readMapper(String path) { File file = new File(path); SAXReader reader = new SAXReader(); MapperBean mapper = new MapperBean(); try { Document doc = reader.read(file); Element root = doc.getRootElement(); //读取根节点 即dao节点 mapper.setInterfaceName(root.attributeValue("id").trim()); //把dao节点的id值存为接口名 List<Function> list = new ArrayList<Function>(); //用来存储方法的List for(Iterator rootIter = root.elementIterator();rootIter.hasNext();) //遍历根节点下所有子节点 { Function fun = new Function(); //用来存储一条方法的信息 Element e = (Element) rootIter.next(); String sqltype = e.getName().trim(); String funcName = e.attributeValue("id").trim(); String sql = e.getText().trim(); String resultType = e.attributeValue("resultType").trim(); fun.setSqltype(sqltype); fun.setFuncName(funcName); fun.setResultType(resultType); fun.setSql(sql); list.add(fun); } mapper.setList(list); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } return mapper; } }
下面写一个Test类测试一下
package com.yck.test; import com.yck.bean.User; import com.yck.dao.IUserDao; import com.yck.yebatis.DaoProxy; public class Test2 { public static void main(String[] args) { IUserDao userdao = (IUserDao) DaoProxy.implDao("src/userdao.xml"); User user= userdao.selectById(2); System.out.println(user); } }
数据库原来有几条数据是这样的
控制台输出结果
package com.yck.dao; import java.util.List; import com.yck.bean.User; public interface IUserDao { User selectById(Integer id); int updateName(String name,Integer id); int deleteById(Integer id); int insert(String name,int age); List<User> getAll(); }
<?xml version="1.0" encoding="UTF-8"?> <dao id="com.yck.dao.IUserDao"> <select id="selectById" resultType ="com.yck.bean.User"> select * from t_user where id = ? </select> <update id="updateName" resultType = "java.lang.Integer"> update t_user set name=? where id=? </update> <delete id="deleteById" resultType = "java.lang.Integer"> delete from t_user where id=? </delete> <insert id="insert" resultType = "java.lang.Integer"> insert into t_user values(null,?,?) </insert> <select id="getAll" resultType = "java.util.List"> select * from t_user; </select> </dao>
import com.yck.bean.User; import com.yck.dao.IUserDao; import com.yck.yebatis.DaoProxy; public class Test2 { public static void main(String[] args) { IUserDao userdao = (IUserDao) DaoProxy.implDao("src/userdao.xml");//通过xml文件生成一个IUserDao实例 User user= userdao.selectById(2); System.out.println(user); userdao.insert("李四", 66); userdao.insert("小明", 66); userdao.insert("小红", 66); userdao.insert("小张", 66); //List<User> list = userdao.getAll(); userdao.updateName("蛋蛋", 1); userdao.deleteById(3); } }
单条的增删改都没问题;问题出在返回没有参数的时候,Object[] 和Object...objects的区别。今天太晚了,先去睡一觉,白天再改善一下吧
<**********************************************************************************************此处是分割线**********************************************************************************************************>
昨晚实在太晚了,为了区别Object[] 和 Object...objects,其实我不明白为什么 Invacation 这个接口里的方法
Object invoke(Object proxy, Method method, Object[] args) throws Throwable 为什么不用可变长数组Object...objects 而且在调用函数的时候它确实有Object...objects的特性比如我们的dao接口里面有像 selectById(Integer id)这样的 单个参数的方法,也有像updateName(String name,Integer id)这样的双参数方法。但是在调用没有参数的getAll()方法时,它报错了,报了空指针错误。这一点我是比较纳闷的,为了解决这个问题我稍微修改了一下用 resultType做判断,如果resultType是java.util.ArrayList类(原谅我实在水平有限,目前只能设计出返回这一种集合。。。),我们再分别判断它有没有参数,为此我在sql语句的标签上加了一个parameter属性,我一开始意思是默认只能为“yes”和“no”两种属性,但我写到这里的时候觉得特别蠢,所以我刚刚默默地去改成了“true”和“false”;最后的最后,修改了以上代码的几句就行了 在mapper.xml中增加了一点标签属性
<?xml version="1.0" encoding="UTF-8"?> <dao id="com.yck.dao.IUserDao"> <select id="selectById" resultType ="com.yck.bean.User" parameter="true"> select * from t_user where id = ? </select> <update id="updateName" resultType = "java.lang.Integer" parameter="true"> update t_user set name=? where id=? </update> <delete id="deleteById" resultType = "java.lang.Integer" parameter="true"> delete from t_user where id=? </delete> <insert id="insert" resultType = "java.lang.Integer" parameter="true"> insert into t_user values(null,?,?) </insert> <select id="getAll" resultType = "java.util.ArrayList" resultOf="com.yck.bean.User" parameter="false"> select * from t_user; </select> </dao>
在DaoProxy类改了读取mapper.xml的代码,如下
/** * 读取xml文件的信息 * @param path * @return */ @SuppressWarnings("rawtypes") private static MapperBean readMapper(String path) { File file = new File(path); SAXReader reader = new SAXReader(); MapperBean mapper = new MapperBean(); try { Document doc = reader.read(file); Element root = doc.getRootElement(); //读取根节点 即dao节点 mapper.setInterfaceName(root.attributeValue("id").trim()); //把dao节点的id值存为接口名 List<Function> list = new ArrayList<Function>(); //用来存储方法的List for(Iterator rootIter = root.elementIterator();rootIter.hasNext();) //遍历根节点下所有子节点 { Function fun = new Function(); //用来存储一条方法的信息 Element e = (Element) rootIter.next(); String sqltype = e.getName().trim(); String funcName = e.attributeValue("id").trim(); String sql = e.getText().trim(); String resultType = e.attributeValue("resultType").trim(); String resultOf = ""; if("java.util.ArrayList".equals(resultType)) resultOf = e.attributeValue("resultOf").trim(); String parameter = e.attributeValue("parameter"); fun.setSqltype(sqltype); fun.setFuncName(funcName); fun.setResultType(resultType); fun.setSql(sql); fun.setResultOf(resultOf); fun.setParameter("true".equals(parameter)); list.add(fun); } mapper.setList(list); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } return mapper; }
修改了实现接口的方法如下
/** * 通过读取配置文件实现dao接口 * @param path * @return */ public static Object implDao(String path) { MapperBean mapper = readMapper(path); ClassLoader classLoader = DaoProxy.class.getClassLoader(); Class interfaze = null; try { interfaze = classLoader.loadClass(mapper.getInterfaceName()); //加载一个接口类 } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } /** * 下面这几句相当重要了,是用xml文件实现dao接口的核心代码,因为数据库相关的大量工作我之前都写过了,所以这个看起来代码量很少 * 我也不太懂下面这个东西,我查API查了相当久,一开始写总是错,因为我理解错了InvocationHandler接口下那个方法的Object数组参数 * 它应该理解为一个可变长数组,而不是必须为数组 */ Object instanze = Proxy.newProxyInstance(classLoader, new Class[]{interfaze}, new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { List<Function> list = mapper.getList(); Object obj = null; for(Function f:list) { if(f.getFuncName().equals(method.getName())) { /** * 判断是不是select语句,是则调用DateUtil的select方法 * 否则调用update的方法 */ if(f.getSqltype().equals("select")) { if("java.util.ArrayList".equals(f.getResultType())) { if(f.isParameter()) obj = DataUtil.selectForBeanList(Class.forName(f.getResultOf()), f.getSql(), args); else obj = DataUtil.selectForBeanList(Class.forName(f.getResultOf()), f.getSql()); } else { if(f.isParameter()) obj = DataUtil.selectForBean(Class.forName(f.getResultType()), f.getSql(), args); else obj = DataUtil.selectForBean(Class.forName(f.getResultType()), f.getSql()); } } else { if(f.isParameter()) obj = DataUtil.updata(f.getSql(), args); else obj = DataUtil.updata(f.getSql()); } } } return obj; } }); return instanze; //返回这个接口,即mapper.getInterfaceName()这个接口 }
最后我们做一下测试,数据库原有的信息如下
测试方法如下
package com.yck.test; import java.util.List; import com.yck.bean.User; import com.yck.dao.IUserDao; import com.yck.yebatis.DaoProxy; public class Test2 { public static void main(String[] args) { IUserDao userdao = (IUserDao) DaoProxy.implDao("src/userdao.xml");//通过xml文件生成一个IUserDao实例 User user= userdao.selectById(2); System.out.println(user); userdao.insert("大牛", 88); userdao.insert("二牛", 77); userdao.insert("三牛", 66); userdao.insert("四牛", 55); List<User> list = userdao.getAll(); System.out.println(list); userdao.updateName("二蛋", 1); userdao.deleteById(3); } }
控制台输出
由于记录数比较多控制台输出太长了,直接贴结果吧
User [id=2, name=王八蛋, age=23] [User [id=1, name=蛋蛋, age=200], User [id=2, name=王八蛋, age=23], User [id=4, name=李四, age=66], User [id=5, name=小明, age=66], User [id=6, name=小红, age=66], User [id=7, name=小张, age=66], User [id=8, name=李三, age=66], User [id=9, name=大牛, age=88], User [id=10, name=二牛, age=77], User [id=11, name=三牛, age=66], User [id=12, name=四牛, age=55]]
数据库最后结果如下