我们先来实现MyBatis和Spring的整合操作。
MyBatis 是一个可以自定义 SQL、存储过程和高级映射的持久层框架。
org.mybatis
mybatis-spring
2.0.4
org.springframework
spring-context
5.1.6.RELEASE
org.springframework
spring-orm
5.1.6.RELEASE
org.springframework
spring-test
5.1.6.RELEASE
use mydb;
drop table if exists t_user;
-- 创建表
create table t_user(
uid int primary key auto_increment,
username varchar(20),
password varchar(20),
phone varchar(11),
address varchar(50)
);
insert into t_user(username,password,phone,address) values('张三','666','18965423548','南阳');
insert into t_user(username,password,phone,address) values('李四','333','18754263548','许昌');
insert into t_user(username,password,phone,address) values('小美','123','18565234759','信阳');
select * from t_user;
package com.zhao.bean;
public class User {
private Integer uid;
private String username;
private String password;
private String phone;
private String address;
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"uid=" + uid +
", username='" + username + '\'' +
", password='" + password + '\'' +
", phone='" + phone + '\'' +
", address='" + address + '\'' +
'}';
}
}
接口类
package com.zhao.dao;
import com.zhao.bean.User;
import java.util.List;
public interface UserDao {
//全查
List selectAll();
}
mapper文件
接口的全路径要和映射文件的namespace保持一致
接口的方法名要和映射文件中的statementId保持一致
接口方法的参数类型,返回类型要和映射文件中的parameterType,resultType保持一致 d
接口和映射文件的名字最好保持一致 例如:UserMapper.java/UserMapper.xml
接口和映射文件最好放到同一个目录
Mybatis 动态 sql 可以让我们在 Xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。
Mybatis 提 供 了 9 种 动 态 sql 标 签 : trim|where|set|foreach|if|choose|when|otherwise|bind。
其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;
Mybatis 在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止 SQL 注入,提高系统安全性。
service接口
package com.zhao.service;
import com.zhao.bean.User;
import java.util.List;
public interface UserService {
List findAll();
}
service接口实现类
package com.zhao.service.impl;
import com.zhao.bean.User;
import com.zhao.dao.UserDao;
import com.zhao.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userDao;
@Override
public List findAll() {
return userDao.selectAll();
}
}
由于整合时相关连接数据库 / 实体类起别名 / 扫描 mapper 文件等操作都在 spring 配置文件中定义, 所以此处只剩日志的配置
添加Spring的配置文件,并在该文件中实现和Spring的整合操作
把MyBatis集成到Spring里面,是为了进一步简化MyBatis的使用,所以只是对MyBatis做了一些封装,并没有替换MyBatis的核心对象。也就是说:MyBatis jar包中的SqlSessionFactory、SqlSession、MapperProxy这些类都会用到。mybatis-spring.jar里面的类只是做了一些包装或者桥梁的工作。
只要我们弄明白了这三个对象是怎么创建的,也就理解了Spring继承MyBatis的原理。我们把它分成三步:
首先我们来看下在MyBatis整合Spring中SqlSessionFactory的创建过程,查看这步的入口在Spring 的配置文件中配置整合的标签中
我们进入SqlSessionFactoryBean中查看源码发现,其实现了InitializingBean 、FactoryBean、ApplicationListener 三个接口
对于这三个接口,学过Spring生命周期的小伙伴应该清楚他们各自的作用
项目 | Value | Value |
接口 | 方法 | 作用 |
FactoryBean | getObject() | 返回由FactoryBean创建的Bean实例 |
InitializingBean | afterPropertiesSet() | bean属性初始化完成后添加操作 |
ApplicationListener | onApplicationEvent() | 对应用的事件进行监听 |
8.1.1.1 afterPropertiesSet
我们首先来看下 afterPropertiesSet 方法中的逻辑
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
可以发现在afterPropertiesSet中直接调用了buildSqlSessionFactory方法来实现 sqlSessionFactory对象的创建
方法小结一下:通过定义一个实现了InitializingBean接口的SqlSessionFactoryBean类,里面有一个afterPropertiesSet()方法会在bean的属性值设置完的时候被调用。Spring在启动初始化这个Bean的时候,完成了解析和工厂类的创建工作。
8.1.1.2 getObject
另外SqlSessionFactoryBean实现了FactoryBean接口。
FactoryBean的作用是让用户可以自定义实例化Bean的逻辑。如果从BeanFactory中根据Bean的ID获取一个Bean,它获取的其实是FactoryBean的getObject()返回的对象。
也就是说,我们获取SqlSessionFactoryBean的时候,就会调用它的getObject()方法。
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
getObject方法中的逻辑就非常简单,返回SqlSessionFactory对象,如果SqlSessionFactory对象为 空的话就又调用一次afterPropertiesSet来解析和创建一次。
8.1.1.3 onApplicationEvent
实现ApplicationListener接口让SqlSessionFactoryBean有能力监控应用发出的一些事件通知。比如 这里监听了ContextRefreshedEvent(上下文刷新事件),会在Spring容器加载完之后执行。这里做的 事情是检查ms是否加载完毕。
public void onApplicationEvent(ApplicationEvent event) {
if (this.failFast && event instanceof ContextRefreshedEvent) {
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
8.1.2.1 DefaultSqlSession的问题
在前面介绍MyBatis的使用的时候,通过SqlSessionFactory的open方法获取的是
DefaultSqlSession,但是在Spring中我们不能直接使用DefaultSqlSession,因为DefaultSqlSession是 线程不安全的。所以直接使用会存在数据安全问题,针对这个问题的,在整合的MyBatis-Spring的插件包中给我们提供了一个对应的工具SqlSessionTemplate。
也就是在我们使用SqlSession的时候都需要使用try catch 块来处理
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
// 或者
SqlSession session = null;
try {
session = sqlSessionFactory.openSession();
// 你的应用逻辑代码
}finally{
session.close();
}
8.1.2.2 SqlSessionTemplate
在mybatis-spring的包中,提供了一个线程安全的SqlSession的包装类,用来替代SqlSession,这个类就是SqlSessionTemplate。因为它是线程安全的,所以可以在所有的DAO层共享一个实例(默认是单例的)。
总结一下:因为DefaultSqlSession自己做不到每次请求调用产生一个新的实例,我们干脆创建一个代理 类,也实现SqlSession,提供跟DefaultSqlSession一样的方法,在任何一个方法被调用的时候都先创建 一个DefaultSqlSession实例,再调用被代理对象的相应方法。
MyBatis还自带了一个线程安全的SqlSession实现:SqlSessionManager,实现方式一样,如果不集成到Spring要保证线程安全,就用SqlSessionManager。
package com.zhao.test;
import com.zhao.bean.User;
import com.zhao.service.UserService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class UserDaoTest {
UserService userService;
@Test
public void testSelectAll(){
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring.xml");
userService=context.getBean(UserService.class);
List userList = userService.findAll();
for (User user:userList){
System.out.println(user);
}
}
}
测试结果
总结一下,Spring是怎么把MyBatis继承进去的?
- 提供了SqlSession的替代品SqlSessionTemplate,里面有一个实现了实现了InvocationHandler的内部SqlSessionInterceptor,本质是对SqlSession的代理。
- 提供了获取SqlSessionTemplate的抽象类SqlSessionDaoSupport。
- 扫描Mapper接口,注册到容器中的是MapperFactoryBean,它继承了SqlSessionDaoSupport,可 以获得SqlSessionTemplate。
- 把Mapper注入使用的时候,调用的是getObject()方法,它实际上是调用了SqlSessionTemplate的getMapper()方法,注入了一个JDK动态代理对象。
- 执行Mapper接口的任意方法,会走到触发管理类MapperProxy,进入SQL处理流程。
核心对象:
对象 | 生命周期 |
SqlSessionTemplate | Spring中SqlSession的替代品,是线程安全的 |
SqlSessionDaoSupport | 用于获取SqlSessionTemplate |
SqlSessionInterceptor(内部类) | 代理对象,用来代理DefaultSqlSession,在SqlSessionTemplate中使用 |
MapperFactoryBean | 代理对象,继承了SqlSessionDaoSupport用来获取SqlSessionTemplate |
SqlSessionHolder | 控制SqlSession和事务 |
设计模式总结:
设计模式 | 类 |
工厂模式 | SqlSessionFactory、ObjectFactory、MapperProxyFactory |
建造者模式 | XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuidler |
单例模式 | SqlSessionFactory、Configuration、ErrorContext |
代理模式 | 绑定:MapperProxy延迟加载:ProxyFactory 插件:PluginSpring集成MyBaits: SqlSessionTemplate的内部SqlSessionInterceptorMyBatis自带连接池:PooledConnection日志打印:ConnectionLogger、StatementLogger |
适配器模式 | Log,对于Log4j、JDK logging这些没有直接实现slf4j接口的日志组件,需要适配器 |
模板方法 | BaseExecutor、SimpleExecutor、BatchExecutor、ReuseExecutor |
装饰器模式 | LoggingCache、LruCache对PerpetualCacheCachingExecutor对其他Executor |
责任链模式 | Interceptor、InterceptorChain |
MyBatis 把 sql 语句从 Java 源程序中独立出来,放在单独的 XML 文件中编写,给程序的维护带来了很大便利。
MyBatis 封装了底层 JDBC API 的调用细节,并能自动将结果集转换成 Java Bean 对象, 大大简化了 Java 数据库编程的重复工作。
因为 MyBatis 需要程序员自己去编写 sql 语句,程序员可以结合数据库自身的特点灵活控制 sql 语句,因此能够实现比 Hibernate 等全自动 orm 框架更高的查询效率,能够完成复杂查询。
接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上@Select@Update 等注解里面包含 Sql 语句来绑定,另外一种就是通过 xml 里面写 SQL 来绑定,在这种情况下,要指定 xml 映射文件里面的 namespace 必须为接口的全路径名.
通过在查询的 sql 语句中定义字段名的别名。
通过