昔洛 的个人博客

Bug不空,誓不成佛

第十篇 MyBatis 与 Spring 的整合
/        

第十篇 MyBatis 与 Spring 的整合

一、整合环境搭建

①准备所需 JAR 包

1.Spring 框架所需的 JAR 包
    Spring 框架所需要准备的 JAR 包共 10 个,其中包括:4个核心模块 JAR,AOP 开发使用的 JAR,JDBC 和事务的 JAR(其中核心容器依赖的 commons-logging 的 JAR 在 MyBatis 框架的 lib 包中已经包含)具体如下:

  • aopalliance-1.0.jar
  • aspectjweaver-1.8.10.jar
  • spring-aop-4.3.6.RELEASE.jar
  • spring-aspects-4.3.6.RELEASE.jar
  • spring-beans-4.3.6.RELEASE.jar
  • spring-context-4.3.6.RELEASE.jar
  • spring-core-4.3.6.RELEASE.jar
  • spring-expression-4.3.6.RELEASE.jar
  • spring-jdbc-4.3.6.RELEASE.jar
  • spring-tx-4.3.6.RELEASE.jar

2.MyBatis 框架所需的 JAR 包
    MyBatis 框架所需要的 JAR 包共13个,其中包括:核心包 mybatis-3.4.2.jar 以及其解压文件中 lib 目录中的所有 JAR,具体如下所示:

  • ant-1.9.6.jar
  • ant-launcher-1.9.6.jar
  • asm-5.1.jar
  • cglib-3.2.4.jar
  • commons-logging-1.2.jar
  • javassist-3.21.0-GA.jar
  • log4j-1.2.17.jar
  • log4j-api-2.3.jar
  • log4j-core-2.3.jar
  • mybatis-3.4.2.jar
  • ognl-3.1.12.jar
  • slf4j-api-1.7.22.jar

3.MyBatis 与 Spring 整合的中间 JAR
    使用中间体 mybatis-spring-1.3.1.jar,也可选择最新版本。
4.数据库驱动 JAR 包
数据库驱动包 mysql-connector-java-5.1.40-bin.jar。
5.数据源所需 JAR 包
整合时所使用的是 DBCP 数据源,所以需要准备 DBCP 和连接池的 JAR 包,具体如下所示。

  • commons-dbcp2-2.1.1.jar
  • commons-pool2-2.4.2.jar

②编写配置文件

    在 Eclipse 中,创建一个新项目,将上一篇项目工程中用的 jar 添加到本次项目的 lib 目录中,并发布到类路径下。
    在项目的 src 目录下,分别创建 db.properties 文件、Spring 的配置文件以及 MyBatis 的配置文件,如下所示:
db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
jdbc.maxTotal=30
jdbc.maxIdle=10
jdbc.initialSize=5

在上述代码中,除配置了连接数据库的基本4项外,还配置了数据库连接池的最大连接(maxTotal)、最大空闲连接数(maxIdle)以及初始化连接数(initialSize)。
applicationContext.xml

<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
		http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	<!-- 读取 db.properties -->
	<context:property-placeholder location="classpath:db.properties"/>
	<!-- 配置数据源 -->
	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
		<!-- 配置数据库驱动 -->
		<property name="driverClassName" value="${jdbc.driver}" />
		<!-- 连接数据库的 url -->
		<property name="url" value="${jdbc.url}" />
		<!-- 连接数据库的用户名 -->
		<property name="username" value="${jdbc.username}"/>
		<!-- 连接数据库的密码 -->
		<property name="password" value="${jdbc.password}"/>
		<!-- 最大连接数 -->
		<property name="maxTotal" value="${jdbc.maxTotal}" />
		<!-- 最大空连接 -->
		<property name="maxIdle" value="${jdbc.maxIdle}"/>
		<!-- 初始化连接数 -->
		<property name="initialSize" value="${jdbc.initialSize}"/>
	</bean>
	<!-- 事务管理器,依赖于数据源 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	<!-- 开启事务注解 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>
	<!-- 配置 MyBatis 工厂 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 注入数据源 -->
		<property name="dataSource" ref="dataSource"/>
		<!-- 指定核心配置文件位置 -->
		<property name="configLocation" value="classpath:mybatis-config.xml" />
	</bean>

上代码中,首先定义了读取 properties 文件配置,然后配置了数据源,接下来配置了事务管理器并开启了事务注解,最后配置了 MyBatis 工厂来与 Spring 整合。其中,MyBatis 工厂的作用就是构建 SqlSessionFactory,它是通过 mybatis-spring 包中提供的 org.mybatis.spring.SqlSessionFactoryBean 类来配置的。通常,在配置时需要提供两个参数:一个是数据源,另一个是 MyBatis 的配置文件路径。这样 Spring 的 IOC 容器就会在初始化 id 为 sqlSessionFactory 的 Bean 时解析 MyBatis 的配置文件,并与数据源一同保存到 Spring 的 Bean 中。
mybatis-config.xml
```XML
<?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>
	<!-- 配置别名 -->
	<typeAliases>
		<package name="com.cn.po"/>
	</typeAliases>
	<!-- 配置 Mapper 的位置 -->
	<mappers>
		...
	</mappers>
</configuration>

由于在 Spring 中已经配置了数据源信息,所以在 MyBatis 的配置文件中就不再需要配置数据源信息。这里只需要使用<typeAliases>和<mappers>元素来配置文件别名以及指定 Mapper 文件位置即可。此外,还需要在项目的 src 目录下创建 log4j.properties 文件,内容参考前面章节。

二、传统 DAO 方式的开发整合

采用传统 DAO 开发方式进行 MyBatis 与 Spring 框架的整合时,需要编写 DAO 接口以及接口的实现类,并且需要向 DAO 实现类中注入 SqlSessionFactory,然后在方法体内通过 SqlSessionFactory 创建 SqlSession。为此,我们可以使用 mybatis-spring 包中所提供的 SqlSessionTemplate 类或 SqlSessionDaoSupport 类来实现此功能。描述如下:

  • SqlSessionTemplate:是 mybatis-spring 的核心类,它负责管理 MyBatis 的 SqlSession,调用 MyBatis 的 SQL 方法。当调用 SQL 方法时,SqlSessionTemplate 将会保证使用的 SqlSession 和当前 Spring 的事务是相关的。它还管理 SqlSession 的生命周期,包含必要的关闭、提交和回滚操作。
  • SqlSessionDaoSupport:是一个抽象支持类,它继承了 DaoSupport 类,主要是作为 DAO 的基类来使用。可以通过 SqlSessionDaoSupport 类的 getSqlSession() 方法来获取所需的 SqlSession。

下面以 SqlSessionDaoSupport 类的使用为例,示例传统的 DAO 开发方式整合的实现,具体步骤如下:
1.实现持久层
(1)在 src 目录下,创建一个 com.cn.po 包,并在包中创建持久化类 Customer,在 Customer 类中定义相关属性和方法后,如下所示:

package com.cn.po;
/*
 * 客户持久化类
 */
public class Customer {
	private Integer id;				//主键 id
	private String username;	//客户名称
	private String jobs;			//职业
	private String phone;		//电话
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getJobs() {
		return jobs;
	}
	public void setJobs(String jobs) {
		this.jobs = jobs;
	}
	public String getPhone() {
		return phone;
	}
	public void setPhone(String phone) {
		this.phone = phone;
	}
	@Override
	public String toString() {
		return "Customer [id=" + id + ", username=" + username + ", jobs=" + jobs + ", phone=" + phone + "]";
	}
}

(2)在 com.cn.po 包中,创建映射文件 CustomerMapper.xml,在该文件中编写根据 id 查询客户信息的映射语句,如下所示:

<?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"> 
<!-- namespace 表示命名空间 -->
<mapper namespace="com.cn.po.CustomerMapper">
	<!-- 根据 id 查询客户信息 -->
	<select id="findCustomerById" parameterType="Integer" resultType="customer">
		select * from t_customer where id=#{id}
	</select>
</mapper>

(3)在 MyBatis 的配置文件 mybatis-config.xml 中,配置映射文件 CustomerMapper.xml 的位置,如下所示:

<mapper resource="com/cn/po/CustomerMapper.xml"/>

2.实现 DAO 层
(1)在 src 目录下,创建一个 com.cn.dao 包,并在包中创建接口 CustomerDao,在接口中编写一个通过 id 查询客户的方法 findCustomerById(),如下所示:

package com.cn.dao;
import com.cn.po.Customer;

public interface CustomerDao {
	//通过 id 查询客户
	public Customer findCustomerById(Integer id);
}

(2)在 src 目录下,创建一个 com.cn.dao.impl 包,并在包中创建CustomerDao 接口的实现类 CustomerDaoImpl,如下所示:

package com.cn.dao.impl;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import com.cn.dao.CustomerDao;
import com.cn.po.Customer;

public class CustomerDaoImpl extends SqlSessionDaoSupport implements CustomerDao{
	//通过 id 查询客户
	public Customer findCustomerById(Integer id) {
		return this.getSqlSession().selectOne("com.cn.po"+".CustomerMapper.findCustomerById",id);
	}
}

在上述代码中,CustomerDaoImpl 类集成了 SqlSessionDaoSupport 类,并实现了 CustomerDao 接口。其中,SqlSessionDaoSupport 类在使用时需要一个 SqlSessionFactory 或一个 SqlSessionTemplate 对象,所以需要通过 Spring 给 SqlSessionDaoSupport 类的子对象注入一个 SqlSessionFactory 或 SqlSessionTemplate。这样,在子类中就能通过调用 SqlSessionDaoSupport 类的 getSqlSession()方法来获取 SqlSession 对象,并使用 SqlSession 对象中的方法了。
(3)在 Spring 的配置文件 applicationContext.xml 中,编写实例化 CustomerDaoImpl 的配置,代码如下:

<!-- 实例化 Dao -->
<bean id="customerDao" class="com.cn.dao.impl.CustomerDaoImpl">
	<!-- 注入 SqlSessionFactory 对象实例 -->
	<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

3.整合测试
在 src 目录下,创建一个 com.cn.test 包,在包中创建测试类 DaoTest,并在类中编写测试方法 findCustomerByIdDaoTest(),如下所示:

package com.cn.test;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.cn.dao.CustomerDao;
import com.cn.po.Customer;
/*
 * Dao 测试类
 */
public class DaoTest {
	@Test
	public void findCustomerByIdDaoTest() {
		ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
		//根据容器中 Bean 的 id 来获取指定的 Bean
		CustomerDao customerDao = (CustomerDao) appc.getBean("customerDao");
		Customer customer = customerDao.findCustomerById(1);
		System.out.println(customer);
	}
}

运行结果如下:
image.png

三、Mapper 接口方式的开发整合

基于 MapperFactorBean 的整合

    MapperFactoryBean 是 MyBatis-Spring 团队提供的一个用于根据 Mapper 接口生成 Mapper 对象的类,该类在 Spring 配置文件中使用时可以配置以下参数。

  • mapperInterface:用于指定接口。
  • sqlSessionFactory:用于指定 SqlSessionFactory。
  • sqlSessionFactory:用于指定 SqlSessionTemplate。如果与 sqlSessionFactory 同时设定,则只会启用 sqlSessionFactory。

接下来通过一个具体的实例通过 MapperFactoryBean 来实现 MyBatis 与 Spring 的第整合,具体步骤如下。
(1)在 src 目录下,创建一个 com.cn.mapper 包,然后在该包中创建 CustomerMapper 接口以及对应的映射文件,编辑如下所示:
CustomerMapper.java:

package com.cn.mapper;
import com.cn.po.Customer;
public interface CustomerMapper {
	//通过 id 查询客户
	public Customer findCustomerById(Integer id);
}

CustomerMapper.xml:

<?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"> 
<!-- namespace 表示命名空间 -->
<mapper namespace="com.cn.mapper.CustomerMapper">
	<!-- 根据 id 查询客户信息 -->
	<select id="findCustomerById" parameterType="Integer" resultType="customer">
		select * from t_customer where id=#{id}
	</select>
</mapper>

(2)在 MyBatis 的配置文件中,引入新的映射文件,代码如下:

<!-- Mapper 接口开发方式 -->
<mapper resource="com/cn/mapper/CustomerMapper.xml" />

(3)在 Spring 的配置文件中,创建一个 id 为 customerMapper 的 Bean,代码如下:

<!-- Mapper 代理开发(基于 MapperFactoryBean) -->
<bean id="customerMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
	<property name="mapperInterface" value="com.cn.mapper.CustomerMapper"/>
	<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

(4)在测试类 DaoTest 中,编写测试方法 findCustomerByIdMapperTest(),其代码如下所示:

@Test
public void findCustomerByIdMapperTest() {
	ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
	CustomerMapper customerMapper = appc.getBean(CustomerMapper.class);
	Customer customer = customerMapper.findCustomerById(1);
	System.out.println(customer);
}

运行结果如下:
image.png

注意:
    Mapper 接口编程方式只需要程序员编写 Mapper 接口(相当于 DAO 接口),然后由 MyBatis 框架根据接口的定义创建接口的动态代理对象。
虽然使用 Mapper 接口编程的方式很简单,但是在具体使用时还是需要遵循一下规范。
(1)Mapper 接口的名称和对应的 Mapper.xml 映射文件的名称必须一致。
(2)Mapper.xml 文件中的 namespace 与 Mapper 接口的类路径相同(即接口文件和映射文件需要放在同一个包中)。
(3)Mapper 接口中的方法名和 Mapper.xml 中定义的每个执行语句的 id 相同。
(4)Mapper 接口中方法的输入参数类型要和 Mapper.xml 中定义的每个 sql 的 resultType 的类型相同。

基于 MapperScannerConfigurer 的整合

    在实际的项目中,DAO 层会包含很多接口,如果每一个接口都要在 Spring 配置文件中配置,那么不但会增加工作量,还会使得 Spring 配置文件非常臃肿,为此,MyBatis-Spring 团队提供了一种自动扫描的形式来配置 MyBatis 中的映射器—采用 MapperScannerConfigurer 类。
MapperScannerConfigurer 类在 Spring 配置文件中使用时可以配置以下几个属性。

  • basePackage:指定映射接口文件所在的包路径,当需要扫描多个包时可以使用分号或逗号作为分隔符。指定包路径后,会扫描该包及其子包中的所有文件。
  • annotationClass:指定了要扫描的注解名称,只有被注解标识的类才会被配置为映射器。
  • sqlSessionFactoryBeanName:指定在 Spring 中定义的 SqlSessionFactory 的 Bean 名称。
  • sqlSessionTemplateBeanName:指定在 Spring 中定义的 SqlSessionTemplate 的 Bean 名称。如果定义此属性,则 sqlSessionFactoryBeanName 将不起作用。
  • markerInterface:指定创建映射器的接口。
    MapperScannerConfigurer 的使用非常简单,只需要在 Spring 的配置文件中编写如下代码:
<!-- Mapper 代理开发(基于 MapperScannerConfigurer) -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.cn.mapper" />
</bean>

在通常情况下,MapperScannerConfigurer 在使用时只需通过 basePackage 属性指定需要扫描的包即可,Spring 会自动地通过包中的接口来生成映射器。
将上一节的第(2)和第(3)步的代码注释掉,再次执行 findCustomerByIdMapperTest() 方法进行测试即可。

测试事务

    在 MyBatis+Spring 的项目中,事务是由 Spring 来管理的。在之前我们已经配置了事务管理器,并开启了事务注解,但是现在还不能够确定事务的配置是否正确,以及事务管能否生效。
在项目中,业务层(Service 层)既是处理业务的地方,又是管理数据库事务的地方。要对事物进行测试,首先需要创建业务层,并在业务层编写添加客户操作的代码;然后在添加操作的代码后,有意地添加一段异常代码(如 int i=1/0;)来模拟现实中的意外情况;最后编写测试方法,调用业务层的添加方法。这样,程序在执行到错误代码时就会出现异常。在没有事务管理的情况下,即使出现了异常,数据也会被存储到数据表中;如果添加了事务管理,并且事务管理的配置正确,那么在执行上述操作时,所添加的数据将不能够插入到数据表中。
下面对上述分析进行实际的编写测试,其具体的实现步骤如下:
(1)在 CustomerMapper 接口中,编写测试方法 addCustomer(),代码如下:

//添加客户
public void addCustomer(Customer customer);

编写完接口中的方法后,在映射文件 CustomerMapper.xml 中编写执行插入操作的 SQL 配置,代码如下:

<!-- 添加客户信息 -->
<insert id="addCustomer" parameterType="customer">
	insert into t_customer(username,jobs,phone)
	values(#{username},#{jobs},#{phone})
</insert>

(2)在 src 目录下,创建一个 com.cn.service 包,并在包中创建接口 CustomerService,在接口中编写一个添加客户的方法 addCustomer(),如下所示:

package com.cn.service;
import com.cn.po.Customer;
public interface CustomerService {
	public void addCustomer(Customer customer);
}

(3)在 src 目录下,创建一个 com.cn.service.impl 包,并在包中创建 CustomerService 接口的实现类 CustomerServiceImpl,来实现接口中的方法,编辑后如下所示:

package com.cn.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cn.mapper.CustomerMapper;
import com.cn.po.Customer;
@Service
//@Transactional
public class CustomerServiceImpl implements CustomerService{
	//注解注入 CustomerMapper
	@Autowired
	private CustomerMapper customerMapper;
	//添加客户
	public void addCustomer(Customer customer) {
		this.customerMapper.addCustomer(customer);
		int i=1/0;//
	}
}

在上述代码中,使用了 Spring 的注解@Service 来标识业务层的类,使用了@Transactional 注解来标识事务处理的类,并通过@Autowired 注解将 CustomerMapper 接口注入到本类中。

提示:
这里先将@Transactional 注解进行了注释,是为了先执行此类没有事务管理的情况,之后删除注释,执行包含事务管理的情况,即可通过结果来验证是否配置成功。

(4)在 Spring 配置文件中,编写开启注解扫描的配置代码,如下所示:

<!-- 开启扫描 -->
<context:component-scan base-package="com.cn.service" />

(5)在 com.cn.test 包中,创建测试类 TransactionalTest,在测试类的 main()方法中编写测试事务执行的代码,如下所示:

package com.cn.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.cn.po.Customer;
import com.cn.service.CustomerService;
/*
 * 测试事务
 */
public class TransactionalTest {
	public static void main(String[] args) {
		ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
		CustomerService customerService = appc.getBean(CustomerService.class);
		Customer customer = new Customer();
		customer.setUsername("zhangsan");
		customer.setJobs("manager");
		customer.setPhone("13233334444");
		customerService.addCustomer(customer);
	}
}

在上述代码中,首先获取了 CustomerService 的实例,然后创建了 Customer 对象,并向对象中添加属性值,最后调用了实例的 addCustomer() 方法执行添加客户操作。
在运行测试方法之前,数据库已有数据如下显示:
image.png
上图可知此时有3条数据,执行测试类中的 main()方法后,输出结果如下图所示:
image.png
可以看出,程序已经执行了插入操作,并且在执行到错误代码时抛出了异常信息,再次查询结果如下:
image.png
从图中可以看出,新添加的数据已经存储在了 t_customer 表中,说明项目中的事务管理没有起作用。此时将 CustomerServiceImpl.java 中的 @Transactional 前面的注释删除,再次执行测试类中的 main() 方法后,虽然仍旧报错,但是此时 t_customer 表中依然只有4条数据,如下所示:
image.png

(゚д゚)σ弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌⊃
评论
歌名 - 歌手
0:00