使用SpringBoot JPA进行自定义的保存及批量保存

更多精彩博文,欢迎访问个人我的博客java


说明

SpringBoot版本:2.1.4.RELEASEmysql

java版本:1.8spring

文中所说JPA皆指spring-boot-starter-data-jpasql

使用JPA保存一个Student对象

在JPA中保存一个对象,仅须要该对象,一个仓储便可。 StudentDO实体类:数据库

@Getter
@Setter
@Entity
@Table(name = "t_student")
public class StudentDO {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private Long id;
    @Column
    private String seq;
    @Column
    private String name;
    @Column
    private int sex;
}
复制代码

JPA仓储:bash

@Repository
public interface StudentRepo extends JpaRepository<StudentDO, Long> {
}
复制代码

通常的,咱们只须要调用StudentRepo.save()方法便可完成对实体对象的保存操做。oracle

@Test
    public void testSave() {
        StudentDO student = new StudentDO();
        student.setName("张三");
        student.setSex(1);
        student.setSeq("123456");
        studentRepo.save(student);
        Assert.assertNotNull(student.getId());
    }
复制代码

在插入过程当中使用mysql函数

若是咱们但愿student的seq值由系统自动生成,且生成规则为“yyMMdd + 8位自增序列”(例如19060310000000)又该如何实现呢?app

首先想到的是该如何生成这一串序列,mysql不像oracle自身支持sequence,所以在这里能够借用函数以及额外的sequence表来实现这一操做,网上有不少实现方式,这里就再也不赘述。函数

如今已经有了函数getseq('student_seq')能够获取到该序列,该如何将其应用到保存对象的方法中?显然的一个问题是,像上面那样再直接调用save方法已经行不通了,应该得须要自定义插入的sql实现。spring-boot

一个容易想到的办法是,在StudentDO类上使用注解@SQLInsert来定义insert的实现,它写起来应该会像这个样子:

@SQLInsert(sql = "INSERT INTO t_student(seq, name, sex) VALUES (getseq('student_seq'), ?, ?")
复制代码

这条sql语句自己并无什么问题,再次调用save()方法也确实可以执行。可是很惋惜,它确会抛出一个sql异常:

java.sql.SQLException: Parameter index out of range (3 > number of parameters, which is 2).
复制代码

显然是程序认为有多少个参数,就得有多少个“?”与之匹配,目前我并无找到解决这个问题的方案,因此这种方法宣告失败。

既然@SQLInsert行不通,或许能够考虑使用@Query注解来自定义一个实现。咱们能够在StudentRepo中定义这样一个方法:

@Transactional
    @Modifying
    @Query(value = "INSERT INTO t_student(seq, name, sex) VALUES (getseq('student_seq'), :#{#student.name}, :#{#student.sex})", nativeQuery = true)
    int insert(@Param("student") StudentDO student);
复制代码

试着运行一下,结果很成功,对象被正常的存储到数据库中,而且seq的取值也正常。看上去咱们的问题已经获得了解决,但事实真的如此么?

自定义的批量存储

上面的例子中,咱们成功经过JPA调用了mysql函数将对象存储到数据库中。但上面的例子只提供了单个保存的方法,若是咱们想批量保存呢?@Query里面的sql可以进行改造么?我并无找到@Query中使用List做为参数的insert方法,可是这并不表明这一操做不能执行。JPA仍旧提供给了使用者原始的使用方式:利用EntityManager来构造sql并执行。

@PersistenceContext
    private EntityManager entityManager;

    private int batchInsert(List<StudentDO> students) {
        StringBuilder sb = new StringBuilder();
        sb.append("INSERT INTO t_student(seq, name, sex) VALUES ");
        for(StudentDO student : students) {
            sb.append("(getseq('student_seq'), ?, ?),");
        }
        String sql = sb.toString().substring(0, sb.length() - 1);
        Query query = entityManager.createNativeQuery(sql);
        int paramIndex = 1;
        for(StudentDO student : students) {
            query.setParameter(paramIndex++, student.getName());
            query.setParameter(paramIndex++, student.getSex());
        }
        return query.executeUpdate();
    }
复制代码

就像MyBatis同样,使用者也能够自定义SQL来执行,试试看,一样没有问题,再多的数据也能够被保存到数据库中!批量保存的效果达到了。

再仔细想想,经过上面的过程,还有什么问题么?对比JPA自带的save()方法,彷佛咱们的自定义保存返回的都是int结果,也就是操做影响的数据库行数。使用过JPA的人都应该了解,JPA的save()方法(或者其余JPA方法)返回的对象是通过持久化的,得益于这一特性,使用者能够在调用save()方法以后获取到对象的id等必须先插入到数据库以后才会有的值。显然这里的操做已经失去了这一特性,那若是咱们把返回值对应的改成Object或者List能够作到么?答案是并不能,咱们会获得以下异常:

org.springframework.dao.InvalidDataAccessApiUsageException: Modifying queries can only use void or int/Integer as return type!; nested exception is java.lang.IllegalArgumentException: Modifying queries can only use void or int/Integer as return type!
复制代码

insert方法必须使用@Modifying进行注解,而@Modifying注解的方法又只能返回int类型的结果。这种状况下或许只能先利用查询获得seq的值再进行操做。

总结

对于JPA的使用还不够了解,一些复杂的状况下没有找到最理想的实现方案。

  1. @Query注解中是否可以使用List以及实现动态拼接参数的效果没有获得解决
  2. 自定义的sql语句返回持久化对象的问题没有方案
  3. 在之后的使用了解中但愿可以找到解决办法,将问题记录在这里,以便后续查看。


    更多精彩博文,欢迎访问个人我的博客

相关文章
相关标签/搜索