玩转 Spring Boot 集成篇(MyBatis、JPA、事务支持)

2022-02-25 09:41:05 浏览数 (1)

在使用 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. 引入依赖

代码语言:javascript复制
<!-- 引入 MyBatis 依赖-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybaits-spring-boot-starter</artifactId>
    <version>2.2.1</version>
</dependency>

1.2. 添加配置

代码语言:javascript复制
# 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. 创建实体类

代码语言:javascript复制
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

代码语言:javascript复制
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);
}
  • @Mapper 注解,MyBatis 会根据接口定义与 Mapper 文件中的 SQL 语句动态创建接口的实现。

1.3.3. 创建 Service 接口

代码语言:javascript复制
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 实现类

代码语言:javascript复制
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 文件

代码语言:javascript复制
<?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. 测试集成

代码语言:javascript复制
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 就轻松搞定,此注解可以用在类上,也可以用在方法上。

  • @Transactional 若注解在类上,那么此类的所有 public 方法都是开启事务的。
  • @Transactional 若注解在方法上面,那么方法级别的注解会覆盖类级别注解。

接下来基于上面第 1 章节的代码稍作改动,便可验证 Spring Boot 事务的支持。

思考:如果保存商品时出现了异常,看看保存的商品能否添加成功?

此刻,在保存商品时,需要人为制造一个空指针异常。

2.1. 不加 @Transactional 注解

代码语言:javascript复制
@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 注解

代码语言:javascript复制
@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. 引入依赖

代码语言:javascript复制
<!-- 引入 JPA 依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

3.2. 添加配置

代码语言:javascript复制
### JPA 配置
# 开启控制台 SQL 输出
spring.jpa.show-sql=true
# 开启格式化 SQL 输出
spring.jpa.properties.hibernate.format_sql=true

3.3. 编写代码

主要完成秒杀商品的查询相关的数据库操作。

3.3.1. 建立 ScProductRepository 接口

代码语言:javascript复制
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. 创建实体类

代码语言:javascript复制
@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 方法
}
  • @Entity:在类的定义中使用 @Entity 注解来进行声明是一个实体 Bean。
  • @Table:声明此对象映射到数据库的数据表,非必须。
  • @Id:指定表的主键。

3.3.3. 编写测试类

代码语言:javascript复制
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 项目如何集成 MyBatis?
  • Spring Boot 项目如何集成 JPA?
  • 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揭秘:快速构建微服务体系》

0 人点赞