赞
踩
我们先来实现MyBatis和Spring的整合操作。
MyBatis 是一个可以自定义 SQL、存储过程和高级映射的持久层框架。
- <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>

- 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<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>
接口的全路径要和映射文件的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<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();
- }
- }

由于整合时相关连接数据库 / 实体类起别名 / 扫描 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>
添加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>

把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<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 |
MyBatis 把 sql 语句从 Java 源程序中独立出来,放在单独的 XML 文件中编写,给程序的维护带来了很大便利。
MyBatis 封装了底层 JDBC API 的调用细节,并能自动将结果集转换成 Java Bean 对象, 大大简化了 Java 数据库编程的重复工作。
因为 MyBatis 需要程序员自己去编写 sql 语句,程序员可以结合数据库自身的特点灵活控制 sql 语句,因此能够实现比 Hibernate 等全自动 orm 框架更高的查询效率,能够完成复杂查询。
接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上@Select@Update 等注解里面包含 Sql 语句来绑定,另外一种就是通过 xml 里面写 SQL 来绑定,在这种情况下,要指定 xml 映射文件里面的 namespace 必须为接口的全路径名.
通过在查询的 sql 语句中定义字段名的别名。
通过<resultMap>来映射字段名和实体类属性名的一一对应的关系。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。