Spring 采用纯注解整合 MyBatis 和 Junit
Spring 整合 MyBatis 和 Junit 等第三方组件,可以通过 XML 配置方式,也可以通过纯注解的配置方式。这里仅仅提供纯注解的配置方式,因为绝大多数情况下,企业开发都采用注解配置方式,因为注解配置比较简单方便,我个人也比较喜欢注解配置方式。
本篇博客不会详细介绍所用到的 Spring 注解,网上资料一大堆,初级开发人员可以自行查找相关资料学习。这里主要是介绍 Spring 如何通过纯注解的方式整合 MyBatis 和 Junit,在本篇博客的最后会提供 Demo 的源代码。
一、搭建工程
新建一个 maven 项目,导入相关 jar 包,我导入的都是最新版本的 jar 包,内容如下:
有关具体的 jar 包地址,可以在 https://mvnrepository.com 上进行查询。
<dependencies>
<!--
Spring 相关的 jar 包:
spring-context 基础核心 jar 包
spring-jdbc 提供数据库访问的基础核心 jar 包
spring-test 提供测试的基本核心 jar 包,用于整合 junit
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.17</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.17</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.17</version>
<scope>test</scope>
</dependency>
<!--
连接操作数据库相关的 jar 包:
mysql-connector-java 连接 mysql 的 jar 包
druid 阿里巴巴的数据库连接池的 jar 包
-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!--
MyBatis 的相关 jar 包:
mybatis-spring 这个由 Mybatis 提供,让 Spring 整合 Mybatis 的 jar 包
-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!--
其它相关 jar 包:
junit 单元测试方法编写所需要的 jar 包
commons-lang3 这个是 apache 提供的实用的公共类工具 jar 包
-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
打开右侧的 Maven 窗口,刷新一下,这样 Maven 会自动下载所需的 jar 包文件。
搭建好的项目工程整体目录比较简单,具体如下图所示:
项目工程结构简单介绍:
config 包下存放的是 Spring 的配置类
dao 包下存放的是 Mybatis 操作数据库的接口类,以及复杂 SQL 语句的方法实现类
domain 包下存放是具体的 Java Bean 实体对象类
service 包下存放的是业务处理实现类
EmployeeApp 这个是该 demo 的 main 入口所在类,使用 Spring 访问数据库,验证搭建成果
resources 目录下存放的是连接数据库的相关参数的配置文件
test 目录下 EmployeeTest 这个是测试方法类,里面编写了测试 EmployeeService 使用 MyBatis 访问数据库的所有方法
二、Spring 整合 MyBatis 的相关配置细节
本 demo 采用只采用一张表 employee 员工表进行演示,数据库是 testdb ,具体创建的 sql 语句内容如下:
# 创建数据库 testdb
CREATE DATABASE IF NOT EXISTS `testdb`;
USE `testdb`;
# 创建数据表 employee
# 字段为:主键id,员工姓名,薪水
CREATE TABLE IF NOT EXISTS `employee` (
`e_id` int(11) NOT NULL AUTO_INCREMENT,
`e_name` varchar(50) DEFAULT NULL,
`e_salary` int(11) DEFAULT NULL,
PRIMARY KEY (`e_id`)
) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8;
# 添加相关的测试数据
INSERT INTO `employee` (`e_id`, `e_name`, `e_salary`) VALUES
(1, '侯胖胖', 25000),
(2, '杨磅磅', 23000),
(3, '李吨吨', 33000),
(4, '任肥肥', 35000),
(5, '乔豆豆', 32000),
(6, '任天蓬', 38000),
(7, '任政富', 40000);
resources 下的 jdbc.properties 配置 mysql 数据库的连接信息
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/testdb?useSSL=false
mysql.username=root
mysql.password=123456
在 config 目录下,JdbcConfig 类是采用 druid 数据库连接池配置数据库连接的类,具体内容如下:
package com.jobs.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
//这里采用 @PropertySource 注解,获取 resources 目录下的 jdbc.properties 文件内容
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
//这里采用 @Value 注解获取 jdbc.properties 文件中每个 key 的配置值
@Value("${mysql.driver}")
private String driver;
@Value("${mysql.url}")
private String url;
@Value("${mysql.username}")
private String userName;
@Value("${mysql.password}")
private String password;
//这里采用 @Bean 注解,表明该方法返回连接数据库的数据源对象
//由于我们只有这一个数据源,因此不需要使用 BeanId 进行标识
@Bean
public DataSource getDataSource(){
//采用阿里巴巴的 druid 数据库连接池的数据源
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
在 Config 目录下 MybatisConfig 类用来配置 SqlSession 工厂对象和定义的访问数据库的 Mapper 接口,具体内容如下:
package com.jobs.config;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.Configuration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MybatisConfig {
//这里的 Bean 由 Spring 根据类型自动调用,因此不需要指定 BeanId
//使用 @Autowired 注解,Spring 自动根据类型将上面的 druid 的数据源赋值到这里
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
//这里配置,将 com.jobs.domain 下的所有 JavaBean 实体类的名字作为别名
//这样 MyBatis 中可以直接使用类名,而不需要使用完全限定名
ssfb.setTypeAliasesPackage("com.jobs.domain");
ssfb.setDataSource(dataSource);
//这里配置,让 MyBatis 在运行时,控制台打印 sql 语句,方便排查问题
Configuration mybatisConfig = new Configuration();
mybatisConfig.setLogImpl(StdOutImpl.class);
ssfb.setConfiguration(mybatisConfig);
return ssfb;
}
//配置 MyBatis 使用 com.jobs.dao 下所有的接口,生成访问数据库的代理类
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.jobs.dao");
return msc;
}
}
在 Config 目录下,SpringConfig 类即为 Spring 的配置类,其将 JdbcConfig 类和 MybatisConfig 类整合在一起。
需要注意的是 SpringConfig 类最好采用 @Import 注解导入其它配置类,不要将相关的配置方法写到这里,否则会出现无法获取配置信息的问题。
package com.jobs.config;
import org.springframework.context.annotation.*;
//@Configuration 这个注解,表明此类是 Spring 的配置类
@Configuration
@ComponentScan("com.jobs")
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
}
三、Spring 采用 MyBatis 操作数据库细节
有了上面 Spring 对 MyBatis 的整合配置,下面 Spring 采用 MyBatis 操作数据库就很简单了,开发人员根本不需要编写访问数据库的任何代码,只需要定义访问数据库的接口,配置接口所需要采用的 SQL 语句即可,大大提高了开发效率。
Employee 实体类的具体内容如下:
package com.jobs.domain;
//注意:为了贴合实际开发场景,这里故意定义的【对象字段名称】与【数据库表字段名称】完全不相同
//在下面的 Dao 接口中,采用 @Results 注解,建立【对象字段名称】与【数据库表字段名称】的对应关系
public class Employee {
private Integer id;
private String name;
private Integer salary;
//建议空参构造函数,必须要有,因为项目中的各种框架一般都会使用
public Employee() {
}
public Employee(Integer id, String name, Integer salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
//这里省去了 3 个字段的 get 和 set 方法.....
//重写 toString 方法,目的在于能够把实体对象打印出来,方便查看
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
在 EmployeeDao 类中,定义访问数据库的方法,MyBatis 会自动根据其定义的接口生成代理类。
这里采用注解的方式直接在 EmployeeDao 类中定义的各个方法上编写 SQL 语句,对于比较复杂的 SQL 语句,可以采用额外的方法进行返回,具体内容如下:
package com.jobs.dao;
import com.jobs.domain.Employee;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface EmployeeDao {
//添加员工
@ResultMap("employee_map")
@Insert("insert into employee(e_id,e_name,e_salary) values(#{id},#{name},#{salary})")
Integer add(Employee emp);
//修改员工信息
@ResultMap("employee_map")
@Update("update employee set e_name=#{name},e_salary=#{salary} where e_id=#{id}")
Integer update(Employee emp);
//查询出所有员工,按照id升序排列
//在 select 方法上定义 employee_map
//建立 Employee 实体类的属性与数据库表 employee 的字段对应关系
@Results(id = "employee_map", value = {
@Result(column = "e_id", property = "id"),
@Result(column = "e_name", property = "name"),
@Result(column = "e_salary", property = "salary")})
@Select("select e_id,e_name,e_salary from employee order by e_id")
List<Employee> selectAll();
//根据条件,查询员工
//这里故意让传递的参数名称不一样,只要使用 @Parm 进行规范即可
@ResultMap("employee_map")
@SelectProvider(type = EmployeeSQL.class, method = "getSelectByConditionSQL")
List<Employee> selectByCondition(@Param("ename") String aaa,
@Param("salaryStart") Integer bbb,
@Param("salaryEnd") Integer ccc);
//传入一个或多个id,删除指定的员工
//这里故意让传递的参数名称不一样,只要使用 @Parm 进行规范即可
@DeleteProvider(type = EmployeeSQL.class, method = "getDeleteByIdsSQL")
Integer deleteByIds(Integer... empids);
}
注意:为了贴合实际开发场景,上面故意将接口中最后 2 个方法的 SQL 编写难度提升,这样就不能直接在接口方法中编写 SQL 语句,必须通过其它类的方法获取相关的 SQL 语句。
对于从其它类的方法获取相关的 SQL 语句的方法,为了贴合实际开发场景,这里故意定义【接口方法的参数名称】与【获取SQL语句的方法的参数名称】完全不一样,从而可以在两个方法上同时使用 @Param 注解,建议参数传递的对应关系。
最后 2 个方法获取相关 SQL 语句的 EmployeeSQL 类内容如下:
package com.jobs.dao;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
public class EmployeeSQL {
//这里故意让传递的参数名称不一样,只要使用 @Parm 进行规范即可
//尽量使用 @Parm 中的参数名称,这样可以防止 SQL 注入攻击
public String getSelectByConditionSQL(@Param("ename") String xxx,
@Param("salaryStart") Integer yyy,
@Param("salaryEnd") Integer zzz) {
StringBuilder sql = new StringBuilder();
sql.append(" select e_id,e_name,e_salary from employee where 1=1");
if (StringUtils.isNotBlank(xxx)) {
sql.append(" and (e_name like CONCAT('%',#{ename},'%'))");
}
if (yyy != null) {
sql.append(" and e_salary >= #{salaryStart}");
}
if (zzz != null) {
sql.append(" and e_salary <= #{salaryEnd}");
}
/*if (StringUtils.isNotBlank(xxx)) {
sql.append(" and (e_name like '%" + xxx + "%')");
}
if (yyy != null) {
sql.append(" and e_salary >=" + yyy);
}
if (zzz != null) {
sql.append(" and e_salary <=" + zzz);
}*/
sql.append(" order by e_id");
return sql.toString();
}
//这里故意让传递的参数名称不一样
//如果不需要防止 sql 注入攻击的话,使用普通参数即可
public String getDeleteByIdsSQL(Integer... ppp) {
StringBuilder sql = new StringBuilder();
sql.append("delete from employee");
if (ppp != null && ppp.length > 0) {
String idsql = StringUtils.join(ppp, ",");
sql.append(" where e_id in (").append(idsql).append(")");
} else {
sql.append(" where 1=2");
}
return sql.toString();
}
}
可以发现上面根本没有编写访问数据库的具体代码,只是定义接口,以及在接口上定义 SQL 语句即可。
然后我们编写 EmployeeService 类中对应的方法,调用接口中的相关方法,实现对数据库的访问。实际场景中 Service 中的一个方法,有可能根据业务需要调用好多 Dao 层中的方法,这里没有什么复杂的业务,所以就每个 Service 方法调用相应的 Dao 方法了,具体内容如下:
package com.jobs.service;
import com.jobs.dao.EmployeeDao;
import com.jobs.domain.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
//采用 @Service 注解将 EmployeeService 的实例化对象添加到 Spring 容器中
//默认是单例模式
@Service("employeeService")
public class EmployeeService {
//采用 @Autowired 将 MyBatis 根据接口动态生成的代理对象注入到这里
@Autowired
private EmployeeDao employeeDao;
public Integer add(Employee emp) {
return employeeDao.add(emp);
}
public Integer update(Employee emp) {
return employeeDao.update(emp);
}
public List<Employee> selectAll() {
return employeeDao.selectAll();
}
public List<Employee> selectByCondition(String name, Integer start, Integer end) {
return employeeDao.selectByCondition(name, start, end);
}
public Integer deleteByIds(Integer... eids) {
return employeeDao.deleteByIds(eids);
}
}
最后我们编写 EmployeeApp 类中的 main 方法,测试一下 Spring 采用纯注解的方式整合 MyBatis 的成果。
package com.jobs;
import com.jobs.config.SpringConfig;
import com.jobs.domain.Employee;
import com.jobs.service.EmployeeService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
public class EmployeeApp {
public static void main(String[] args) {
//通过注解配置类获取 Spring 容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//从 Spring 容器中获取 EmployeeService 对象
EmployeeService empService = (EmployeeService) ctx.getBean("employeeService");
//调用其获取所有员工的方法,并打印获取的结果
List<Employee> emplist = empService.selectAll();
for (Employee emp : emplist) {
System.out.println(emp);
}
}
}
执行后的结果如下图所示:
四、Spring 整合 Junit 相关细节
Spring 工程搭建完毕后,想要测试其中的一些方法,确实不太方便,比如本 demo 就必须得在 EmployeeApp 中的 main 方法中进行编码测试。为了能够更好的编写测试方法,我们就必须让 Spring 整合 Junit ,这样就能够在独立的测试类中,通过 Spring 容器注入相关的对象,从而测试对象的方法调用。
Spring 整合 Junit 非常简单,主要就两步:导入 2 个 jar 包 和 使用 2 个注解。
第一步:导入 2 个 jar 包。
在本篇博客的最开始,已经在 pom.xml 文件中导入 2 个相应的 jar 包,具体内容为:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.17</version>
<scope>test</scope>
</dependency>
第二步:使用 2 个注解。
@RunWith(SpringJUnit4ClassRunner.class) :这个是固定写法,这里不做过多解释。
@ContextConfiguration(classes = Spring 的配置类) :指定 Spring 的配置类。
在本 demo 中 Spring 的配置类是 SpringConfig 类。
根据上面 2 个步骤,我们编写 Spring 整合 Junit 的测试类 EmployeeTest,具体内容如下:
package com.jobs;
import com.jobs.config.SpringConfig;
import com.jobs.domain.Employee;
import com.jobs.service.EmployeeService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class EmployeeTest {
//由于 Spring 已经整合了 Junit
//因此这里可以直接使用 @Autowired 注解根据类型注入 EmployeeService 对象
@Autowired
private EmployeeService employeeService;
//添加三个新员工信息
@Test
public void add() {
Employee emp1 = new Employee(100, "候菲特", 50000);
Integer result1 = employeeService.add(emp1);
System.out.println(result1);
Employee emp2 = new Employee(101, "任盖茨", 60000);
Integer result2 = employeeService.add(emp2);
System.out.println(result2);
Employee emp3 = new Employee(102, "李政富", 70000);
Integer result3 = employeeService.add(emp3);
System.out.println(result3);
}
//修改一名员工的信息
@Test
public void update() {
Employee emp = new Employee(100, "任首富", 80000);
Integer result = employeeService.update(emp);
System.out.println(result);
}
//查询所有员工
@Test
public void selectAll() {
List<Employee> emplist = employeeService.selectAll();
System.out.println(emplist);
}
//根据相关条件查询筛选员工
@Test
public void selectByCondition() {
String name = "任";
int start = 30000;
int end = 60000;
List<Employee> emplist = employeeService.selectByCondition(name, start, end);
System.out.println(emplist);
}
//删除指定id的一个或多个员工
@Test
public void deleteByIds() {
Integer result = employeeService.deleteByIds(100, 101, 102);
System.out.println(result);
}
}
到此为止,已经完成了 Spring 采用纯注解的方式整合 MyBatis 和 Junit,并且在方法执行过程中能够打印出所执行的 SQL 语句,方便查看和排查问题。
本 demo 贴合实际开发,故意制造了一些麻烦,并进行解决,Demo 代码经过测试没有任何问题,方便大家参考。
最后提供本 Demo 的源代码:https://files.cnblogs.com/files/blogs/699532/spring_mybatis_junit.zip