1.MyBatis整合Spring实现
我们先来实现MyBatis和Spring的整合操作。
1.1什么事MyBatis?
MyBatis 是一个可以自定义 SQL、存储过程和高级映射的持久层框架。
1.2添加相关的依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
2.创建数据库
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;
3.创建项目并导入所需jar包,并创建结构如下
4.创建实体类
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 + '\'' +
'}';
}
}
5.创建dao层的接口及其mapper映射文件
接口类
package com.zhao.dao;
import com.zhao.bean.User;
import java.util.List;
public interface UserDao {
//全查
List<User> selectAll();
}
mapper文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhao.dao.UserDao">
<select id="selectAll" resultType="user">
select * from t_user;
</select>
</mapper>
5.1 Mapper DAO层开发规范
-
接口的全路径要和映射文件的namespace保持一致
-
接口的方法名要和映射文件中的statementId保持一致
-
接口方法的参数类型,返回类型要和映射文件中的parameterType,resultType保持一致 d
-
接口和映射文件的名字最好保持一致 例如:UserMapper.java/UserMapper.xml
-
接口和映射文件最好放到同一个目录
5.2 Mybatis 动态 sql 是做什么的?都有哪些动态 sql?能简述一下动态 sql 的执行原理不?
-
Mybatis 动态 sql 可以让我们在 Xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。
-
Mybatis 提 供 了 9 种 动 态 sql 标 签 : trim|where|set|foreach|if|choose|when|otherwise|bind。
-
其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
5.3 #{}和${}的区别是什么?
- #{}是预编译处理,${}是字符串替换。
-
Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;
-
Mybatis 在处理${}时,就是把${}替换成变量的值。
-
使用#{}可以有效的防止 SQL 注入,提高系统安全性。
6.创建service接口及其实现类
service接口
package com.zhao.service;
import com.zhao.bean.User;
import java.util.List;
public interface UserService {
List<User> 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<User> findAll() {
return userDao.selectAll();
}
}
7.创建mybatis配置文件
由于整合时相关连接数据库 / 实体类起别名 / 扫描 mapper 文件等操作都在 spring 配置文件中定义, 所以此处只剩日志的配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--1.配置mybatis的运行 此处配置运行时使用log4j-->
<settings>
<setting name="logImpl" value="log4j"/>
</settings>
</configuration>
8.创建Spring配置文件
添加Spring的配置文件,并在该文件中实现和Spring的整合操作
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描注解:1)在service的接口实现类定义注解 2)dao接口没有实现类,直接在接口上定义
注解,通过下来自动获得代理对象-->
<context:component-scan base-package="com.zhao" />
<!--1.定义连接数据库的数据源DriverManagerDataSource:实际开发使用第三方连接池管理数据
源-->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--2.在IOC容器中定义SqlSessionFactoryBean,配置数据源-->
<bean id="factoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="dataSource"/>
<!--给实体类起别名-->
<property name="typeAliasesPackage" value="com.zhao.bean" />
<!--加载mybatis的核心配置:如果不需要设置mybatis的运行配置,则不需要加载-->
<property name="configLocation" value="mybatis.xml" />
</bean>
<!--3.在IOC容器中定义MapperScannerConfigurer 用来扫描dao层接口的mapper文件-->
<bean id="scanner"
class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zhao.dao"/>
</bean>
</beans>
8.1 整合Spring的原理
把MyBatis集成到Spring里面,是为了进一步简化MyBatis的使用,所以只是对MyBatis做了一些封装,并没有替换MyBatis的核心对象。也就是说:MyBatis jar包中的SqlSessionFactory、SqlSession、MapperProxy这些类都会用到。mybatis-spring.jar里面的类只是做了一些包装或者桥梁的工作。
只要我们弄明白了这三个对象是怎么创建的,也就理解了Spring继承MyBatis的原理。我们把它分成三步:
- SqlSessionFactory在哪创建的。
- SqlSession在哪创建的。
- 代理类在哪创建的。
8.1.1 SqlSessionFactory
首先我们来看下在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 SqlSession
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。
9.创建测试类
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<User> 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 |
9.MyBatis 的好处是什么?
-
MyBatis 把 sql 语句从 Java 源程序中独立出来,放在单独的 XML 文件中编写,给程序的维护带来了很大便利。
-
MyBatis 封装了底层 JDBC API 的调用细节,并能自动将结果集转换成 Java Bean 对象, 大大简化了 Java 数据库编程的重复工作。
-
因为 MyBatis 需要程序员自己去编写 sql 语句,程序员可以结合数据库自身的特点灵活控制 sql 语句,因此能够实现比 Hibernate 等全自动 orm 框架更高的查询效率,能够完成复杂查询。
10.接口绑定有几种实现方式,分别是怎么实现的?
接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上@Select@Update 等注解里面包含 Sql 语句来绑定,另外一种就是通过 xml 里面写 SQL 来绑定,在这种情况下,要指定 xml 映射文件里面的 namespace 必须为接口的全路径名.
11.当实体类中的属性名和表中的字段名不一样,如果将查询的结果封装到指定 pojo?
-
通过在查询的 sql 语句中定义字段名的别名。
-
通过<resultMap>来映射字段名和实体类属性名的一一对应的关系。