深入Mybatis
1 前言
大家都知道MyBatis是我们常用的一个持久层框架,那么今天我们就来深入MyBatis底层,来探究MyBatis到底是怎么实现数据库操作的呢? 2 持久层的开发模式
1 传统的开发模式
接口定义业务方法 public interface UserService { public User getUserById(int id); }
实现类实现接口方法 (sql语句写在java代码里面) public class UserServiceImpl implements UserService{ @Override public User getUserById(int id) { Connection conn = JDBCTools.getConnection(); String sql = "select * from user where id = ?"; PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = conn.prepareStatement(sql); pstmt.setInt(1, id); rs = pstmt.executeQuery(); if(rs.next()){ int sid = rs.getInt(1); String name = rs.getString(2); User user = new User(sid,name); return user; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ JDBCTools.release(conn, pstmt, rs); } return null; } }
测试 @Test public void f1() throws Exception{ UserService userService = new UserServiceImpl(); User user = userService.getUserById(1); System.out.println(user); }
2 Mybatis的开发模式
定义接口方法 public interface UserMapper { public User getUserById(int id); }
接口对应的映射xml文件(sql语句写在xml文件) <?xml version="1.0" encoding="UTF-8" ?>
测试 @Test public void f1() throws Exception{ UserMapper mapper = (UserMapper) new MyInvocationHandler().getInstance(UserMapper.class); User user = mapper.getUserById(1); System.out.println(user); }
通过以上代码可以看到,MyBatis的方式省去了实现类的创建,改为用xml来定义业务方法的具体实现
为什么MyBatis可以不用通过实例化对象来操作我们的代码呢?这里就不得不说说jdk的动态代理
jdk动态代理运行时结合接口和mapper.xml来动态创建一个代理对象,程序调用该代理对象的方法来完成业务 3 jdk动态代理
创建一个类,实现InvocationHandler接口
1 自定义getInstance方法:入参为目标对象,通过Proxy.newProxyInstance方法创建代理对象,并返回
2 实现接口的invoke方法,通过反射机制完成业务逻辑代码
invoke方法是核心代码,在该方法中实现具体的业务需求
3 使用invoke方法解析数据库信息配置xml,创建数据库连接对象 //读取数据源配置信息 public static Map getProperties(){ Map map = new HashMap(); SAXReader reader = new SAXReader(); try { Document document = reader.read("src/config.xml"); //获取根节点 Element root = document.getRootElement(); Iterator iter = root.elementIterator(); while(iter.hasNext()){ Element e = (Element) iter.next(); //解析environments节点 if("environments".equals(e.getName())){ Iterator iter2 = e.elementIterator(); while(iter2.hasNext()){ //解析environment节点 Element e2 = (Element) iter2.next(); Iterator iter3 = e2.elementIterator(); while(iter3.hasNext()){ Element e3 = (Element) iter3.next(); //解析dataSource节点 if("dataSource".equals(e3.getName())){ if("POOLED".equals(e3.attributeValue("type"))){ Iterator iter4 = e3.elementIterator(); //获取数据库连接信息 while(iter4.hasNext()){ Element e4 = (Element) iter4.next(); map.put(e4.attributeValue("name"),e4.attributeValue("value")); } } } } } } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return map; } //获取信息,创建数据源对象 Map map = ParseXML.getProperties(); ComboPooledDataSource datasource = new ComboPooledDataSource(); datasource.setDriverClass(map.get("driver")); datasource.setJdbcUrl(map.get("url")); datasource.setUser(map.get("username")); datasource.setPassword(map.get("password")); datasource.setInitialPoolSize(20); datasource.setMaxPoolSize(40); datasource.setMinPoolSize(2); datasource.setAcquireIncrement(5); Connection conn = datasource.getConnection();4 反射
数据库连接,接下来就需要获取待执行的SQL语句,sql的定义全部写在UserMapper.xml中;解析xml执行sql语句,执行完毕,查询结果会保存在ResultSet中,还需要将ResultSet对象中的数据进行解析,封装到JavaBean中返回
上述步骤通过两步实现
第一步:反射机制创建User对象
第二步:通过反射动态执行类中所有属性的setter方法,完成赋值 //获取sql语句 String sql = element.getText(); //获取参数类型 String parameterType = element.attributeValue("parameterType"); //创建pstmt PreparedStatement pstmt = createPstmt(sql,parameterType,conn,args); ResultSet rs = pstmt.executeQuery(); if(rs.next()){ //读取返回数据类型 String resultType = element.attributeValue("resultType"); //反射创建对象 Class clazz = Class.forName(resultType); obj = clazz.newInstance(); //获取ResultSet数据 ResultSetMetaData rsmd = rs.getMetaData(); //遍历实体类属性集合,依次将结果集中的值赋给属性 Field[] fields = clazz.getDeclaredFields(); for(int i = 0; i < fields.length; i++){ Object value = setFieldValueByResultSet(fields[i],rsmd,rs); //通过属性名找到对应的setter方法 String name = fields[i].getName(); name = name.substring(0, 1).toUpperCase() + name.substring(1); String MethodName = "set"+name; Method methodObj = clazz.getMethod(MethodName,fields[i].getType()); //调用setter方法完成赋值 methodObj.invoke(obj, value); } }
代码的实现大致思路如上所述,具体实现起来有很多细节需要处理 5 工具类
上述操作使用的两个工具类完整代码如下
ParseXML public class ParseXML { //读取数据源配置信息 public static Map getProperties(){ Map map = new HashMap(); SAXReader reader = new SAXReader(); try { Document document = reader.read("src/config.xml"); //获取根节点 Element root = document.getRootElement(); Iterator iter = root.elementIterator(); while(iter.hasNext()){ Element e = (Element) iter.next(); //解析environments节点 if("environments".equals(e.getName())){ Iterator iter2 = e.elementIterator(); while(iter2.hasNext()){ //解析environment节点 Element e2 = (Element) iter2.next(); Iterator iter3 = e2.elementIterator(); while(iter3.hasNext()){ Element e3 = (Element) iter3.next(); //解析dataSource节点 if("dataSource".equals(e3.getName())){ if("POOLED".equals(e3.attributeValue("type"))){ Iterator iter4 = e3.elementIterator(); //获取数据库连接信息 while(iter4.hasNext()){ Element e4 = (Element) iter4.next(); map.put(e4.attributeValue("name"),e4.attributeValue("value")); } } } } } } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return map; } //根据接口查找对应的mapper.xml public static String getMapperXML(String className){ //保存xml路径 String xml = ""; SAXReader reader = new SAXReader(); Document document; try { document = reader.read("src/config.xml"); Element root = document.getRootElement(); Iterator iter = root.elementIterator(); while(iter.hasNext()){ Element mappersElement = (Element) iter.next(); if("mappers".equals(mappersElement.getName())){ Iterator iter2 = mappersElement.elementIterator(); while(iter2.hasNext()){ Element mapperElement = (Element) iter2.next(); //com.cehnjie.dao.StudentDAO . 替换 # className = className.replace(".", "#"); //获取接口结尾名 String classNameEnd = className.split("#")[className.split("#").length-1]; String resourceName = mapperElement.attributeValue("resource"); //获取resource结尾名 String resourceName2 = resourceName.split("/")[resourceName.split("/").length-1]; //StudentDAO.xml . 替换 # resourceName2 = resourceName2.replace(".", "#"); String resourceNameEnd = resourceName2.split("#")[0]; if(classNameEnd.equals(resourceNameEnd)){ xml="src/"+resourceName; } } } } } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } return xml; } }
MyInvocationHandler public class MyInvocationHandler implements InvocationHandler{ private String className; public Object getInstance(Class cls){ //保存接口类型 className = cls.getName(); Object newProxyInstance = Proxy.newProxyInstance( cls.getClassLoader(), new Class[] { cls }, this); return (Object)newProxyInstance; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SAXReader reader = new SAXReader(); //返回结果 Object obj = null; try { //获取对应的mapper.xml String xml = ParseXML.getMapperXML(className); Document document = reader.read(xml); Element root = document.getRootElement(); Iterator iter = root.elementIterator(); while(iter.hasNext()){ Element element = (Element) iter.next(); String id = element.attributeValue("id"); if(method.getName().equals(id)){ //获取信息,创建数据源对象 Map map = ParseXML.getProperties(); ComboPooledDataSource datasource = new ComboPooledDataSource(); datasource.setDriverClass(map.get("driver")); datasource.setJdbcUrl(map.get("url")); datasource.setUser(map.get("username")); datasource.setPassword(map.get("password")); datasource.setInitialPoolSize(20); datasource.setMaxPoolSize(40); datasource.setMinPoolSize(2); datasource.setAcquireIncrement(5); Connection conn = datasource.getConnection(); //获取sql语句 String sql = element.getText(); //获取参数类型 String parameterType = element.attributeValue("parameterType"); //创建pstmt PreparedStatement pstmt = createPstmt(sql,parameterType,conn,args); ResultSet rs = pstmt.executeQuery(); if(rs.next()){ //读取返回数据类型 String resultType = element.attributeValue("resultType"); //反射创建对象 Class clazz = Class.forName(resultType); obj = clazz.newInstance(); //获取ResultSet数据 ResultSetMetaData rsmd = rs.getMetaData(); //遍历实体类属性集合,依次将结果集中的值赋给属性 Field[] fields = clazz.getDeclaredFields(); for(int i = 0; i < fields.length; i++){ Object value = setFieldValueByResultSet(fields[i],rsmd,rs); //通过属性名找到对应的setter方法 String name = fields[i].getName(); name = name.substring(0, 1).toUpperCase() + name.substring(1); String MethodName = "set"+name; Method methodObj = clazz.getMethod(MethodName,fields[i].getType()); //调用setter方法完成赋值 methodObj.invoke(obj, value); } } conn.close(); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return obj; } /** * 根据条件创建pstmt * @param sql * @param parameterType * @param conn * @param args * @return * @throws Exception */ public PreparedStatement createPstmt(String sql,String parameterType,Connection conn,Object[] args) throws Exception{ PreparedStatement pstmt = null; try { switch(parameterType){ case "int": int start = sql.indexOf("#{"); int end = sql.indexOf("}"); //获取参数占位符 #{name} String target = sql.substring(start, end+1); //将参数占位符替换为? sql = sql.replace(target, "?"); pstmt = conn.prepareStatement(sql); int num = Integer.parseInt(args[0].toString()); pstmt.setInt(1, num); break; case "java.lang.String": int start2 = sql.indexOf("#{"); int end2 = sql.indexOf("}"); String target2 = sql.substring(start2, end2+1); sql = sql.replace(target2, "?"); pstmt = conn.prepareStatement(sql); String str = args[0].toString(); pstmt.setString(1, str); break; default: Class clazz = Class.forName(parameterType); Object obj = args[0]; boolean flag = true; //存储参数 List
为啥你家儿子总是听不到你说话?做好4点,孩子乖乖听讲还很开心说了一万遍,孩子就是听不到,怎么办呀?如果你也有这样的烦恼,看完这篇文章,肯定有很大的收获。干货有点多,记不住没关系,收藏这篇文章反复观看就能内化于心了。妈妈炒完菜从厨房出来,提醒
刺客伍六七师姐的人设是最大败笔?不过是导演在转移话题罢了!刺客伍六七动画如今已经更新到第七集啦,看到现在,发现很多小伙伴都觉得江慧莲的人设人设崩塌了,我倒觉得与其将动画观感差归咎于江师姐的人设崩塌,倒不如说是人设与故事逻辑不兼容和细节填充
征服者康死了吗?蚁人3导演作出回应目前,漫威电影蚁人与黄蜂女量子狂潮(简称蚁人3)上映差不多也有10天时间了。相信平时关注漫威的粉丝,基本上都已经看过这部电影。在蚁人3正片结局,征服者康的高科技装备由于遭到千万只蚂
老年人走路腿软无力是怎么回事老年人走路腿软无力一般是由于气虚血虚阳虚等原因引起的,建议老人及时就医,进行相应的治疗。1气虚是指由于元气不足引起的一系列表现,主要症状有身体虚弱面色苍白呼吸短促手脚无力发懒头晕动
比肉还补!5分钟上桌,补足蛋白质!春天长个子,多给孩子吃!江南的春天,小雨淅沥,尤其早晚凉飕飕的,感觉甚至比冬天还冷!真心不想早起,可是,娃开学了学生娃的早餐,就喜欢这种不用发酵,搅拌搅拌直接就可以搞定的款式!不仅蛋白质丰富,5分钟就能上
宫保鸡丁这样做才好吃,鸡肉鲜嫩,酸甜微辣,开胃下饭又下酒头条创作挑战赛人们常说没有什么事情是一顿美食解决不了的,如果有,那就再吃两顿,虽然说得有些夸张,但确实美食是每个人都抵挡不了的。不论生活中遇到什么烦恼,美食都是人们最好的慰藉,味蕾
诺基亚近60年来首次换Logo标志性蓝色消失,摆脱手机厂商形象诺基亚推出全新Logo。当地时间2月26日,在巴塞罗那举行的世界移动通信大会期间,曾经的手机巨头诺基亚宣布更换其企业标识,以便重塑以往手机生产者的印象。这是该公司在近60年来首次更
表现越来越稳定,勇士在今年夏天应该很难再留住这位后场大将了?在今日的一场NBA常规赛中,主场作战的金州勇士末节发力以109比104逆转战胜了明尼苏达森林狼。本场比赛后,拿下了两连胜的勇士的战绩提升到了31胜30负,排名也上升到了西部第7位而
人间草木最情深只知道汪曾祺是一个非常雅致的人,但是并不是特别喜欢其作品,没有共鸣产生,怎么也读不进去。但是这天翻看网络的时候,突然看到一句话如果你来访我,我不在,请和我门外的花坐一会儿,它们很温
富文此刻便是人间好时节来源淳安融媒体中心阳光洒落的日子带来了一丝春的气息清风拂过雪坑的农院里飘散出阵阵沁人心脾的梅花香在花间的树梢上挂上盏盏红灯笼红与白的点缀相伴让阖家团圆的小院多了几分心驰神往梅花是冬
如何唤醒倦怠熬夜肌?假期结束开工已经有一段时间了,放假时不规律的作息和油腻的饮食让我们的皮肤状态非常不稳定,加上工作后的熬夜加班和不健康的外卖让很多姐妹都出现了各种皮肤问题。所以如何根据皮肤问题选择合