在使用 Spring 进行实际项目研发中,Spring 整合 ORM 组件(MyBatis、JPA)是必不可少一个环节,而在整合过程中,往往要进行大量的配置。
借助 SpringBoot 可以屏蔽 Spring 整合 ORM 组件配置的大量简化,进而让研发人员更加专注于业务逻辑的开发,使得企业级项目开发更加快速和高效。
本文将重点分享 Spring Boot 与两种常用的 ORM 组件的整合:MyBatis 和 JPA,顺带提一嘴 Spring Boot 的事务支持。
1. Spring Boot 集成 MyBatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。 MyBatis 特点:简单易学、灵活、解除sql与程序代码的耦合、提供映射标签,支持对象与数据库的orm字段关系映射、提供对象关系映射标签,支持对象关系组建维护、提供xml标签,支持编写动态sql等。
1.1. 引入依赖
<!-- 引入 MyBatis 依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybaits-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
1.2. 添加配置
# MySQL 链接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
## MyBatis 的配置
# Mapper资源文件存放的路径
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
# Dao 接口文件存放的目录
mybatis.type-aliases-package=com.example.demo.dao
# 开启 debug,输出 SQL
logging.level.com.example.demo.dao=debug
1.3. 编写代码
主要完成秒杀商品的添加、查询相关的数据库操作。
1.3.1. 创建实体类
package com.example.demo.model;
import java.io.Serializable;
import java.util.Date;
/**
* 商品类
* @author caicai
*/
public class ScProduct implements Serializable {
private Integer id;
private String name;
private String productImg;
private Integer number;
private Date startTime;
private Date endTime;
private Date createTime;
// 提供 setter、getter 方法
}
1.3.2. 创建Dao
package com.example.demo.dao;
import com.example.demo.model.ScProduct;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ScProductDao {
/** 查询指定编号的商品信息*/
ScProduct findByProductId(Integer id);
/** 保存商品信息*/
int insert(ScProduct scProduct);
}
1.3.3. 创建 Service 接口
package com.example.demo.service;
import com.example.demo.model.ScProduct;
public interface ScProductService {
/** 查询指定编号的商品信息*/
ScProduct findByProductId(Integer id);
/** 保存商品信息*/
int save(ScProduct scProduct);
}
1.3.4. 创建 Service 实现类
package com.example.demo.service.impl;
import com.example.demo.dao.ScProductDao;
import com.example.demo.model.ScProduct;
import com.example.demo.service.ScProductService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class ScProductServiceImpl implements ScProductService {
@Resource
ScProductDao scProductDao;
@Override
public ScProduct findByProductId(Integer id) {
return scProductDao.findByProductId(id);
}
@Override
public int save(ScProduct scProduct) {
int saveRes = scProductDao.insert(scProduct);
System.out.println("向数据库插入:" + scProduct);
return saveRes;
}
}
1.3.5. 创建 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.example.demo.dao.ScProductDao">
<resultMap id="BaseResultMap" type="com.example.demo.model.ScProduct">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="number" property="number" jdbcType="INTEGER"/>
<result column="start_time" property="startTime" jdbcType="TIMESTAMP"/>
<result column="end_time" property="endTime" jdbcType="TIMESTAMP"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="product_img" property="productImg" jdbcType="VARCHAR"/>
</resultMap>
<select id="findByProductId" resultMap="BaseResultMap">
select * from sc_product where id = #{id,jdbcType=INTEGER}
</select>
<insert id="insert" parameterType="com.example.demo.model.ScProduct">
insert into sc_product (id, name, number,start_time,end_time,create_time,product_img)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR},#{number,jdbcType=INTEGER},
#{startTime,jdbcType=TIMESTAMP}, #{endTime,jdbcType=TIMESTAMP},#{createTime,jdbcType=TIMESTAMP},
#{productImg,jdbcType=VARCHAR})
</insert>
</mapper>
1.4. 测试集成
package com.example.demo;
import com.example.demo.model.ScProduct;
import com.example.demo.service.ScProductService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.Date;
@SpringBootTest
public class MyBatisTests {
@Resource
ScProductService scProductService;
@Test
public void saveTest() {
ScProduct sp = new ScProduct();
sp.setId(6);
sp.setName("ihpone 16 512g 玫瑰红");
sp.setStartTime(new Date());
sp.setEndTime(new Date());
sp.setCreateTime(new Date());
sp.setNumber(100);
sp.setProductImg("./ihpne16.png");
int saveRes = scProductService.save(sp);
System.out.println("向数据库添加商品结果:" + (saveRes == 1 ? "成功" : "失败"));
}
@Test
public void findByProductId() {
Integer id = 6;
ScProduct sp = scProductService.findByProductId(id);
System.out.println("编号为 " + id + "的商品信息为:" + sp);
}
}
执行 saveTest 单元验证,输出如下:
执行 findByProductId 单元验证,输出如下:
至此,Spring Boot 与 MyBatis 便集成完毕。不过那多 Mpper 文件、那么多实体、那么多 Service 等要编写代码,也挺繁琐,其实这些都是可以自动生成的,不过不是本文的分享重点(捂嘴笑),接下来谈谈 Spring Boot 事务的支持。
2. Spring Boot 事务的支持
Spring Boot 开启事务的方式很简单,只需要一个注解 @Transactional 就轻松搞定,此注解可以用在类上,也可以用在方法上。
接下来基于上面第 1 章节的代码稍作改动,便可验证 Spring Boot 事务的支持。
思考:如果保存商品时出现了异常,看看保存的商品能否添加成功?
此刻,在保存商品时,需要人为制造一个空指针异常。
2.1. 不加 @Transactional 注解
@Override
public int save(ScProduct scProduct) {
int saveRes = scProductDao.insert(scProduct);
System.out.println("向数据库插入:" + scProduct);
// 制造一个异常,看看事务是否回滚
String str = null;
System.out.println(str.length());
return saveRes;
}
为了方便观察结果,执行单元测试之前,需要从库中把 Id 为 6 的商品给删除掉,然后执行单元测试。
执行 saveTest 单元测试,向数据库保存 Id 为 6 的商品,执行结果如下:
执行 findByProductId 单元测试,查询 Id 为 6 的商品是否存在,执行结果如下:
很显然,不是预期的效果,如果保存商品 service 出现了异常,不应该保存成功才对,那就需要配置 @Transactional 注解。
2.2. 添加 @Transactional 注解
@Transactional
@Override
public int save(ScProduct scProduct) {
int saveRes = scProductDao.insert(scProduct);
System.out.println("向数据库插入:" + scProduct);
// 制造一个异常,看看事务是否回滚
String str = null;
System.out.println(str.length());
return saveRes;
}
为了方便观察结果,执行单元测试之前,需要从库中把 Id 为 6 的商品给删除掉,然后执行单元测试。
执行 saveTest 单元测试,向数据库保存 Id 为 6 的商品,执行结果如下:
执行 findByProductId 单元测试,查询 Id 为 6 的商品是否存在,执行结果如下:
很显然,当保存商品的 service 方法执行出现异常时,商品添加失败,符合心理预期。
@Transactional 注解在类上,那么此类的所有 public 方法都是开启事务的,对于本文的效果是一样的,不再赘述。
3. Spring Boot 集成 JPA
JPA 是 Java Persistence API 的简称,中文名 Java 持久层 API,是 JDK 5.0 注解或 XML 描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。Sun引入新的 JPA ORM 规范出于两个原因:其一,简化现有 Java EE 和 Java SE 应用开发工作;其二,Sun 希望整合 ORM 技术,实现天下归一。 JPA 实现:Hibernate3.2+、TopLink 10.1.3 以及 OpenJPA。
Spring Data JPA 简化数据层的代码,进而让研发人员更加专注业务逻辑的实现。若要在 SpringBoot 中使用 Spring Data JPA,需要如下简单几步便可集成。
3.1. 引入依赖
<!-- 引入 JPA 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
3.2. 添加配置
### JPA 配置
# 开启控制台 SQL 输出
spring.jpa.show-sql=true
# 开启格式化 SQL 输出
spring.jpa.properties.hibernate.format_sql=true
3.3. 编写代码
主要完成秒杀商品的查询相关的数据库操作。
3.3.1. 建立 ScProductRepository 接口
package com.example.demo.repository;
import com.example.demo.model.ScProduct;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* 商城商品Repository
*/
public interface ScProductRepository extends JpaRepository<ScProduct, Integer> {
}
3.3.2. 创建实体类
@Entity
@Table(name="sc_product")
public class ScProduct implements Serializable {
@Id
private Integer id;
@Column
private String name;
@Column
private String productImg;
@Column
private Integer number;
@Column
private Date startTime;
@Column
private Date endTime;
@Column
private Date createTime;
// setter/ getter 方法
}
3.3.3. 编写测试类
package com.example.demo;
import com.example.demo.model.ScProduct;
import com.example.demo.repository.ScProductRepository;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Optional;
@SpringBootTest
public class JpaRepositoryTest {
@Resource
ScProductRepository scProductRepository;
@Test
public void jpaTest() {
// 测试向数据库插入记录
ScProduct scProduct = new ScProduct();
scProduct.setId(4);
scProduct.setName("iphone 13 256g 玫瑰金");
scProduct.setNumber(99);
scProduct.setProductImg("./iphone13_256g.png");
scProduct.setCreateTime(new Date());
scProduct.setEndTime(new Date());
scProduct.setStartTime(new Date());
scProduct = scProductRepository.save(scProduct);
System.out.println("商品编号为:" + scProduct.getId());
// 测试从数据库查询记录
Integer id = 4;
Optional<ScProduct> sp = scProductRepository.findById(id);
System.out.println("商品编号为" + id + " 的商品信息为:" + sp.get());
}
}
3.4. 测试集成
执行单元测试,控制台输出如下。
至此,Spring Boot 与 JPA 集成完毕。
回头捋捋,若要在 SpringBoot 中使用 Spring Data JPA,貌似就只用声明持久层的接口,其它的都交给 Spring Data JPA 来完成了,可谓快哉。
4. 例行回顾
本文是 Spring Boot 项目集成持久层组件篇的讲解,主要分享了如下部分:
玩转 Spring Boot 集成持久层组件就写到这里,希望大家能够喜欢。
追逐技术的道路上,发扬袋鼠精神「从不后退、永远前行」。停止精神消耗,做有价值的事;希望做的事情如钉钉子,钉一个是一个,实实在在。
一起聊技术、谈业务、喷架构,少走弯路,不踩大坑,会持续输出更多精彩分享
参考资料:
https://spring.io/
https://start.spring.io/
https://spring.io/projects/spring-boot
https://github.com/spring-projects/spring-boot
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
https://stackoverflow.com/questions/tagged/spring-boot
《Spring Boot从入门到实战》《深入浅出Spring Boot 2.x》
《一步一步学Spring Boot:微服务项目实战(第二版)》
《Spring Boot揭秘:快速构建微服务体系》