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操作 之后只需要将容器回收就可以了