------------恢复内容开始------------
mybatis现在已经是一大主流框架,相比hibernate来说 mybatis的使用更加广泛 当然这也有mybatis入手简单的原因,不过也无法改变使用的人越来越多
我门都知道jdbc的连接数据库方式 其实不论是mybatis也好还是其他的用于数据库连接的对象映射框架都是基于JDBC来进行封装的
下面是一段jdbc的代码 我们可以通过解读这段代码来逐步了解mybatis
1 Connection connection = null; 2 PreparedStatement preparedStatement = null; 3 ResultSet resultSet = null; 4 try { 5 // 加载数据库驱动 6 Class.forName("com.mysql.jdbc.Driver"); 7 // 通过驱动管理类获取数据库链接 8 connection = 9 DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root1234"); 10 // 定义sql语句?表示占位符 11 String sql = "select * from user where username = ?"; 12 // 获取预处理statement 13 preparedStatement = connection.prepareStatement(sql); 14 // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 15 preparedStatement.setString(1, "王五"); 16 // 向数据库发出sql执行查询,查询出结果集 17 resultSet = preparedStatement.executeQuery(); 18 // 遍历查询结果集 19 User user = new User(); 20 while (resultSet.next()) { 21 int id = resultSet.getInt("id"); 22 String username = resultSet.getString("username"); 23 // 封装User 24 user.setUsername(username); 25 user.setId(id); 26 } 27 System.out.println(user); 28 } catch (Exception e) { 29 e.printStackTrace(); 30 } finally { 31 // 释放资源 32 if (resultSet != null) { 33 try { 34 resultSet.close(); 35 } catch (SQLException e) { 36 e.printStackTrace(); 37 } 38 } 39 if (preparedStatement != null) { 40 try { 41 preparedStatement.close(); 42 } catch (SQLException e) { 43 e.printStackTrace(); 44 } 45 } 46 47 }
首先
// 加载数据库驱动 8.0以后的驱动为com.mysql.cj.jdbc.Driver Class.forName("com.mysql.jdbc.Driver"); // 通过驱动管理类获取数据库链接 connection =DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root1234");
这段我想使用过mybatis的应该都不会陌生 就是jdbc配置文件中配置的driver url user password
mybatis通过解析xml配置文件中的<dataSource>标签内的<property>标签内容来获取这些驱动信息,之后将其封装用于连接数据库时的构建连接
再往下
// 定义sql语句?表示占位符 String sql = "select * from user where username = ?"; // 获取预处理statement preparedStatement = connection.prepareStatement(sql); // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 preparedStatement.setString(1, "王五"); // 向数据库发出sql执行查询,查询出结果集 resultSet = preparedStatement.executeQuery(); // 遍历查询结果集 User user = new User(); while (resultSet.next()) { int id = resultSet.getInt("id"); String username = resultSet.getString("username"); // 封装User user.setUsername(username); user.setId(id); } System.out.println(user);
这一段就是具体的sql操作了
对照一个mapper.xml来看
<select id="findByCondition" resultType="com.lagou.pojo.User" paramterType="com.lagou.pojo.User"> select * from user where id = #{id} and username = #{username} </select>
mybatis将定义sql语句放在了select标签中
这时我们会发现没有看到mybatis的statement预处理对象 其实statement对象是被mybatis封装了 这个后面会说到
然后就是参数和返回结果的设置 mybatis是用paramterType标明了参数和返回对象类型 具体的封装过程并没有看到
后返回结果 我们发现mybatis会多出一个id这个属性
用过的都知道id需要同方法名 这样为了方便精准定位 具体怎么定位 我们稍后再说
最后是释放资源 也被mybatis封装了
// 释放资源 if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } }
所以mybatis的使用我们只需要定义需要定义的内容即可 不必重复进行其他操作
下面我们来谈谈mybatis是怎么进行这些操作的
参考jdbc代码 首先我们要解析驱动 ,同时因为mapper.xml这些也是写在配置文件中 所以我们还需要解析这些配置文件 那么就需要两个解析类来解析这两个配置文件
1 package com.lagou.config; 2 3 import com.lagou.io.Resources; 4 import com.lagou.pojo.Configuration; 5 import com.mchange.v2.c3p0.ComboPooledDataSource; 6 import org.dom4j.Document; 7 import org.dom4j.DocumentException; 8 import org.dom4j.Element; 9 import org.dom4j.io.SAXReader; 10 11 import java.beans.PropertyVetoException; 12 import java.io.InputStream; 13 import java.util.List; 14 import java.util.Properties; 15 16 public class XMLConfigBuilder { 17 18 private Configuration configuration; 19 20 public XMLConfigBuilder() { 21 this.configuration = new Configuration(); 22 } 23 24 /** 25 * 该方法就是使用dom4j对配置文件进行解析,封装Configuration 26 */ 27 public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException { 28 29 Document document = new SAXReader().read(inputStream); 30 //<configuration> 31 Element rootElement = document.getRootElement(); 32 List<Element> list = rootElement.selectNodes("//property"); 33 Properties properties = new Properties(); 34 for (Element element : list) { 35 String name = element.attributeValue("name"); 36 String value = element.attributeValue("value"); 37 properties.setProperty(name,value); 38 } 39 40 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); 41 comboPooledDataSource.setDriverClass(properties.getProperty("driver")); 42 comboPooledDataSource.setJdbcUrl(properties.getProperty("url")); 43 comboPooledDataSource.setUser(properties.getProperty("username")); 44 comboPooledDataSource.setPassword(properties.getProperty("password")); 45 46 configuration.setDataSource(comboPooledDataSource); 47 48 //mapper.xml解析: 拿到路径--字节输入流---dom4j进行解析 49 List<Element> mapperList = rootElement.selectNodes("//mapper"); 50 51 for (Element element : mapperList) { 52 String mapperPath = element.attributeValue("resource"); 53 InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath); 54 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); 55 xmlMapperBuilder.parse(resourceAsSteam); 56 57 } 58 59 60 61 62 return configuration; 63 } 64 65 66 }
首先这个类来解析config配置 将取道的dataSource封装到Configuration对象中 方便之后取出
1 package com.lagou.config; 2 3 import com.lagou.pojo.Configuration; 4 import com.lagou.pojo.MappedStatement; 5 import org.dom4j.Document; 6 import org.dom4j.DocumentException; 7 import org.dom4j.Element; 8 import org.dom4j.io.SAXReader; 9 10 import java.io.InputStream; 11 import java.util.ArrayList; 12 import java.util.List; 13 14 public class XMLMapperBuilder { 15 16 private Configuration configuration; 17 18 public XMLMapperBuilder(Configuration configuration) { 19 this.configuration =configuration; 20 } 21 22 public void parse(InputStream inputStream) throws DocumentException { 23 24 Document document = new SAXReader().read(inputStream); 25 Element rootElement = document.getRootElement(); 26 27 String namespace = rootElement.attributeValue("namespace"); 28 29 List<Element> selectList = rootElement.selectNodes("//select"); 30 List<Element> insertList = rootElement.selectNodes("//insert"); 31 List<Element> updateList = rootElement.selectNodes("//update"); 32 List<Element> deleteList = rootElement.selectNodes("//delete"); 33 34 ArrayList<Element> list = new ArrayList<>(); 35 list.addAll(selectList); 36 list.addAll(insertList); 37 list.addAll(updateList); 38 list.addAll(deleteList); 39 for (Element element : list) { 40 String id = element.attributeValue("id"); 41 String resultType = element.attributeValue("resultType"); 42 String paramterType = element.attributeValue("paramterType"); 43 String sqlText = element.getTextTrim(); 44 MappedStatement mappedStatement = new MappedStatement(); 45 mappedStatement.setId(id); 46 mappedStatement.setResultType(resultType); 47 mappedStatement.setParamterType(paramterType); 48 mappedStatement.setSql(sqlText); 49 String key = namespace+"."+id; 50 configuration.getMappedStatementMap().put(key,mappedStatement); 51 52 } 53 54 } 55 56 57 }
然后时通过这个类来mapper配置文件中的属性也放到Configuration对象中 ,这里就要谈到map的key值问题了 从代码中可以看出是通过namespace+.+id来得到的 至于namespace是哪里定义的
就是mapper.xml的最外层配置的namespace属性中,通常我们的设置是mapper接口的全路径
之后我们就可以通过Configuration对象取出driver信息以及sql信息了 这时我们只需要statement对象来进行操作就行了 最后释放
这里这样写没什么问题 但是比较笨 而且每次活得对象每次释放的行为太过繁琐和重复
那么我们就需要给他封装一个可以执行sql的执行器 Executor
然后让Executor去帮我们完成这些工作 我们取到结果,那么Executor对象中将这些取到后操作 那么换一个接口不是要重新封装一次
所以我们通过jdk的动态代理对象的方式为mapper接口生成代理对象 并返回
1 public <T> T getMapper(Class<?> mapperClass, String type) { 2 // 使用JDK动态代理来为Dao接口生成代理对象,并返回 3 4 Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() { 5 @Override 6 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 7 // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne 8 // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名 9 // 方法名:findAll 10 String methodName = method.getName(); 11 String className = method.getDeclaringClass().getName(); 12 13 String statementId = className+"."+methodName; 14 15 // 准备参数2:params:args 16 // 获取被调用方法的返回值类型 17 Type genericReturnType = method.getGenericReturnType(); 18 if (null != type || "".equals(type)) { 19 // 判断是否进行了 泛型类型参数化 20 if (genericReturnType instanceof ParameterizedType && "select".equals(type)) { 21 List<Object> objects = selectList(statementId, args); 22 return objects; 23 } 24 if ("insert".equals(type)) { 25 Integer result = insert(statementId, args); 26 return result; 27 } 28 if ("update".equals(type)) { 29 Integer result = update(statementId, args); 30 return result; 31 } 32 if ("delete".equals(type)) { 33 Integer result = delete(statementId, args); 34 return result; 35 } 36 return selectOne(statementId,args); 37 } 38 39 return null; 40 41 } 42 }); 43 44 return (T) proxyInstance; 45 }
我这里是通过type参数来判断该方法要执行什么操作的 不过我后来考虑可以直接取mapper标签来进行这一步操作 这样的话 就不会因为输入错误的type导致操作偏差了(因为我写修改的方法是忘了改delete的type所以测试update的时候执行了delete操作)
之后我们再在Executor中去具体执行jdbc操作就可以直接返回结果了
执行完之后关于释放连接的问题 mybatis是通过sqlSession来控制的
mybatis通过工厂设计模式的方法 去实现sqlSessionFactory来产出一个个sqlSession对象作为容器 在容器内执行上面的Executor操作 之后只需要将容器回收就可以了
------------恢复内容结束------------
浙公网安备 33010602011771号