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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!