【java学习-Spring】Spring-data-jpa(Java Persistence API)和Template

1,概念

1)JPA

场景:整合第三方ORM框架,建立一种标准的方式ORM 访问数据库的统一。
现阶段JPA几乎都是接口,实现都是Hibernate在做。我们都知道,在使用持久化工具的时候,一般都有一个对象来操作数据库,在原生的Hibernate中叫做Session,在MyBatis中叫做SqlSession,而在JPA中叫做EntityManager通过这个对象来操作数据库。

对象关系映射(Object Relational Mapping,简称ORM)
通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。

2)占位符

JPA里占位符:?1(必须按顺序传参)或 :userName (推荐,本地sql中出现的冒号需要通过双斜杠转义)。

3)JSR338(Java Specification Requests,JPA规范)

JSR 338主要定义了如何通过普通的Java domain进行关系数据库的操作及映射,概括如下:

1>Entity

  • 必须是顶级类
  • @Entity注解的类
  • 必须有一个无参的public 或 protected的构造方法
  • 不能是final类,且不能有final方法或final变量
  • 一个Entity类通常与数据库的一张表进行对应
  • 一个Entity实例表现为数据库的一条数据
  • 对Entity的操作即对数据库的操作
  • 生命周期包含初始、托管、释放、消亡

2>EntityManager

  • 对Entity持久化操作的主要对象
  • 通过EntityManagerFactory获取实例
  • 一个实例代表一个数据库连接
  • 每个线程拥有自己的EntityManager实例
  • 主要方法有persist、remove、merge、createQuery、find
@PersistenceContext
private  EntityManager em;

3>EntityManagerFactory

  • 创建EntityManager的工厂
  • EntityManagerFactory的创建成本很高,对于给定的数据库,系统应该只创建一个与之关联的Factory
  • 可使用@PersistenceUnit注入

4> EntityTransaction

  • 表示数据库事务,在增、删、改时使用
  • 可通过EntityManager.getTransaction()获取

5>Persistence Context

  • 维护一组托管状态的Entity实例
  • 与EntityManager是相关联的

6>Persistence Unit

  • 一组Entity及相关设置的逻辑单元
  • 定义创建EntityManagerFactory所需要的参数
  • 通过persistence.xml定义或者通过一个PersistenceUnitInfo对象

7>总结

通过Persistence Unit创建EntityManagerFactory,再通过EntityManagerFactory获取EntityManager。

2,EntityManager

EntityManager是JPA中用于增删改查的接口,它的作用是:对一组实体类(Entity Bean)与底层数据源(tabel或临时表)之间进行 O/R 映射的管理。

1)Entity生命周期

在这里插入图片描述

状态名描述作为java对象存在在实体管理器中存在在数据库存在
New(瞬时对象)尚未有id,还未和Persistence Context建立关联的对象yesnono
Managed(持久化受管对象)有id值,已经和Persistence Context建立了关联的对象yesyesyes
Detached(游离态离线对象)有id值,但没有和Persistence Context建立关联的对象nonono
Removed(删除的对象)有id值,尚且和Persistence Context有关联,但是已经准备好从数据库中删除yesyesno

2)方法

1> 新增数据:em.persist(Object entity);

如果entity的主键不为空,而数据库没有该主键,会抛出异常;
如果entity的主键不为空,而数据库有该主键,且entity的其他字段与数据库不同,persist后不会更新数据库;

2> 根据主键查找数据:em.find(Class entityClass, Object primaryKey);

如果主键格式不正确,会抛出illegalArgumentException异常;
如果主键在数据库未找到数据返回null;

3>删除数据:em.remove(Object entity);

只能将Managed状态的Entity实例删除,由此Entity实例状态变为Removed;

4> 将Detached状态的Entity实例转至Managed状态:em.merge(T entity);

5> 将所有的Entity实例状态转至Detached状态:em.clear();

场景举例:

从数据查出对象A
B.list=A.list
修改B.list
save(B)

最后发现往往A在数据库种的值也跟着改变了,为避免这种情况,在每次save之前em.clear() 一下

6> 将所有Managed状态的Entity实例同步到数据库:em.flush();

7> 刷新获得最新Entity:em.refresh(Object entity);

加载Entity实例后,数据库该条数据被修改,refresh该实例,能得到数据库最新的修改,覆盖原来的Entity实例。

8> 获取Session对象:em.unwrap(Session.class)

3)托管方式

A. 容器托管(EntityManger && PersistenceContext)

    @PersistenceContext 也可以用@Autowired注解。
    EntityManager em;

@PersistenceContext是jpa专有的注解(推荐),而@Autowired是spring自带的注释。
@AutowiredEntityManager不是线程安全的,当多个请求进来的时候,spring会创建多个线程,而@PersistenceContext就是用来为每个线程创建一个EntityManager的,而@Autowired就只创建了一个,为所有线程共用,有可能报错。
B. 应用托管(EntityManagerFactory && PersistenceUnit)

EntityManagerFactory 接口主要用来创建 EntityManager 实例。该接口约定了如下4个方法:
createEntityManager():用于创建实体管理器对象实例。
createEntityManager(Map map):用于创建实体管理器对象实例的重载方法,Map 参数用于提供 EntityManager 的属性。
isOpen():检查 EntityManagerFactory 是否处于打开状态。实体管理器工厂创建后一直处于打开状态,除非调用close()方法将其关闭。
close():关闭 EntityManagerFactoryEntityManagerFactory 关闭后将释放所有资源,isOpen()方法测试将返回 false,其它方法将不能调用,否则将导致IllegalStateException异常。

2,原理

1)Repository接口

在这里插入图片描述

Repository

最顶层的接口,是一个空接口,目的是为了统一所有的Repository的类型,且能让组件扫描时自动识别。

CrudRepository

Repository的子接口,提供CRUD 的功能。

PagingAndSortingRepository

CrudRepository的子接口, 添加分页排序。

JpaRepository

PagingAndSortingRepository的子接口,增加批量操作等。

常用方法
delete删除或批量删除 
findOne查找单个 
findAllByIdAndName查找所有 
save保存单个或批量保存 
saveAndFlush保存并刷新到数据库
countByInvestUserId
方法说明
dao.flush ()同步持久上下文环境,即将持久上下文环境的所有未保存实体的状态信息保存到数据库中。
dao.refresh (Object entity)用数据库实体记录的值更新实体对象的状态,即更新实例的属性值。
dao.clear ()清除持久上下文环境,断开所有关联的实体。如果这时还有未提交的更新则会被撤消。
dao.contains (Object entity)判断一个实例是否属于当前持久上下文环境管理的实体。
dao.isOpen ()判断当前的实体管理器是否是打开状态。
dao.getTransaction ()返回资源层的事务对象。EntityTransaction实例可以用于开始和提交多个事务。
dao.close ()关闭实体管理器。之后若调用实体管理器实例的方法或其派生的查询对象的方法都将抛出 IllegalstateException 异常,除了getTransaction 和 isOpen方法(返回 false)。不过,当与实体管理器关联的事务处于活动状态时,调用 close 方法后持久上下文将仍处于被管理状态,直到事务完成。

JpaSpecificationExecutor

用来做复杂查询的接口。

2)启动过程

由于引入了starter-data-jpa,自动配置,spring启动时会实例化一个Repositories,然后扫描包,找出所有继承Repository的接口(除@NoRepositoryBean注解的类),遍历装配。为每一个接口创建相关实例。
SimpleJpaRespositry——用来进行默认的DAO操作,是所有Repository的默认实现
JpaRepositoryFactoryBean——装配bean,装载了动态代理Proxy,会以对应的DAO的beanName为key注册到DefaultListableBeanFactory中,在需要被注入的时候从这个bean中取出对应的动态代理Proxy注入给DAO
JdkDynamicAopProxy——动态代理对应的InvocationHandler,负责拦截DAO接口的所有的方法调用,然后做相应处理,比如findByUsername被调用的时候会先经过这个类的invoke方法

导入jpa,自动配置扫描包内所有继承了Repository接口的接口,为每一个接口实例一个代理类(SimpleJpaRepository),并为每个方法确定一个query策略,已经其他所需的bean,使用自定的repository时,是使用的代理类,经过一些列的拦截器后,选取一个query执行器JpaQueryExecution,然后创建Query对象,拼接sql,组装参数等DB操作,最后返回Query.getResultList()。

3,使用

1)maven配置以及相关数据库依赖配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.1.7.RELEASE</version>
</dependency>

2)实体类及注解

@Table(name=userinfoTab) 
@Entity 
public class Userinfo implements Serializable {
    @Id  //映射到数据库表的主键属性
    @GeneratedValue(strategy=GenerationType.AUTO)  //主键的生成策略(自增)
    private Long user_id;
    private String user_name;
    @Column(length = 255) //配置单列属性
    private String user_tel;
    @Transient
    private String new_time;
	...
}

1>@Entity

  1. 类注解;
  2. 声明实体类;
  3. 不可缺省

2>@Table(name=userinfoTab)

  1. 类注解;
  2. 指定数据块表映射;
参数说明
name表名,缺省以类名做表名
catalog设置表所属的数据库目录
schema设置表所属的模式

3>@Id

  1. 主键注解;
  2. 不可缺省,除非采用复合主键;
    对于复合主键有2种实现方式:@idClass@EmbeddedId

4>@GeneratedValue

@GeneratedValue(strategy=GenerationType.AUTO) 
  1. 主键注解;与@Id一起使用
  2. 主键是自动生成策略:
策略说明备注
IDENTITY自增方式Oracle 不支持这种方式;
AUTO默认值;JPA自动选择合适的策略;默认情况下,SqlServer 对应 identity,MySQL 对应 auto increment。
SEQUENCE通过序列产生主键,通过 @SequenceGenerator 注解指定序列名MySql 不支持这种方式
TABLE通过表产生主键,使用该策略可以使应用更易于数据库移植。

5>@idClass(复合主键)

a. 复合主键类

编写一个复合主键的类CustomerPK,代码如下:CustomerPK.java
作为符合主键类,要满足以下几点要求。

  1. 必须实现Serializable接口。
  2. 必须有默认的public无参数的构造方法。
  3. 必须覆盖equals和hashCode方法。

@Data自动生成构造函数: CustomerPK(),CustomerPK(String name, String email);set,get方法;hashCode() ,equals(Object obj) 方法。
equals方法用于判断两个对象是否相同,EntityManger通过find方法来查找Entity时,是根据equals的返回值来判断的。本例中,只有对象的name和email值完全相同时或同一个对象时则返回true,否则返回false。hashCode方法返回当前对象的哈希码,生成的hashCode相同的概率越小越好,算法可以进行优化。

import java.io.Serializable;
//复合主键对象
@Data
public class CustomerPK implements Serializable {

         private String email;
         private String name;
}
b. @IdClass注解

通过@IdClass注释在实体中标注复合主键,实体代码如下:

@Data
@Entity
@Table(name = "customer")
@IdClass(CustomerPK.class)
public class CustomerEO implements java.io.Serializable {

         private Integer id;     
         @Id
         private String name;
         @Id
         private String email;

6>@EmbeddedId(复合主键)

  1. @Embeddable注释复合主键类
@Embeddable
@Data
public class DependentId implements Serializable {
    private String name;
    private int id
}
  1. @EmbeddedId引入复合主键
@Entity
@Data
public class Employee {

    @EmbeddedId
    private EmployeeId ids;
    private String name;
    private String email;
}

7>@Embedded

想让两个类的属性生成一个数据表,在一个类里这样加入另一个类即可:

@Embedded 
private C c; 

8>@MappedSuperclass

一个类继承另一个类的所有属性,则在父类里这样写:

@SuppressWarnings("serial") 
@Entity 
@MappedSuperclass   //增加这一行 
并把父类的所有属性的private改为protected即可 

9> 对象关联关系 @OneToOne @ManyToOne

@OneToOne 并通过@JoinColumn指定了表之间的外键关系

@Entity
public class User{
	@OneToOne
	@JoinColumn(name="address_id")
	private Address address;
}

@ManyToOne 并通过@JoinColumn指定了表之间的外键关系
@OneToMany 并通过@JoinColumn指定了表之间的外键关系
@ManyToMany 并通过@JoinTable指定了表之间的外键关系

@Entity
public class Product{
	@ManyToMany 
	//以关联表product_catalog表示关系。
	@JoinTable(name="product_catalog",joinColumns = @JoinColumn(name = "product_id"), inverseJoinColumns = @JoinColumn(name="catalog_id"))
	private Set<Catalog> catalogs = new HashSet<Catalog>();
}
@OneToMany(cascade = CascadeType.ALL, mappedBy = "a", fetch = FetchType.EAGER)
  1. fetch属性:
    值| 说明
    –|:–
    FetchType.EAGER | 即时加载;默认。
    FetchType.LAZY | 懒加载

  2. optional属性
    表示该属性是否允许为null, 默认为true

10>@Basic

表示属性到数据库表字段的简单映射,可缺省。

11>@Column

  1. 字段注解;
  2. 声明数据 库字段和类属性对应关系
属性说明缺省
name显示指定数据块映射字段;按字段名驼峰转下划线映射
unique
nullable
length

12>@Transient

ORM框架将忽略该字段,不映射数据库字段。

13>@Temporal

调整精度: DATE, TIME, 和 TIMESTAMP

@Temporal(TemporalType.TIMESTAMP)// 时间戳
    public Date getCreatedTime() {
        return createdTime;
        }

@Temporal(TemporalType.DATE) //时间精确到天
    public Date getBirth() {
        return birth;
    }

14>@Lob

声明字段为 Clob 或 Blob 类型。
对应pg中的oid数据类型。

3)Repository接口及注解

JPQL(面向对象的SQL语法结构)

public interface UserinfoRepository extends JpaRepository<Userinfo, Long> {
    @Modifying
    @Query(value = "update userinfo set user_name = :name, user_tel = :tel where user_id = :id",nativeQuery = true)
    @Transactional
    void updateById(@Param("id") Long id, @Param("name") String name, @Param("tel") String tel);

    @Query(value = "select user_id from userinfo",nativeQuery = true)
    List<Long> getUserIdList();
}

以上类通常放在dao包下面。
外部使用时:

    @Autowired
    ThreatRepository threatRepository;
    threatRepository.saveAll(list);
@Query(绑定自定义sql到自定义方法上)

当进行 find 操作时,JPA 在 EntityManager 中缓存了 find 生成的对象,当再次 find 时会直接返回该对象。
@Query 引起的数据库变更 EntityManager 并不能发现,更进一步说,使用其它工具或者其它框架修改数据库中的数据,也不能及时反应到 JPA 的 find 系列方法上来。

@Modifying(标注@Query绑定的sql涉及增删改)

加上这个注解,JPA会以更新类语句来执行,而不再是以查询语句执行。 
该注解中有两个属性:

  1. flushAutomatically:自动刷新,即执行完语句后立即将变化内容从缓存刷新到磁盘。
  2. clearAutomatically:自动清除缓存,即执行完语句后自动清除掉已经过期的实体,比如,我们删除了一个实体,但是在还没有执行flush操作时,这个实体还存在于实体管理器EntityManager中,但这个实体已经过期没有任何用处,直到flush操作时才会被删除掉。如果希望在删除该实体时立即将该实体从实体管理器中删除,则可以将该属性设置为true,会在更新完数据库后会主动清理一级缓存。
    自动清理之后还会带来一个新的问题,clear 操作清理的缓存中,还包括提交后未 flush 的数据,例如调用 save 而不是 saveAndFlush 就有可能不会立即将修改内容更新到数据库中,在 save 之后 flush 之前调用 @Modifying(clearAutomatically = true) 修饰的方法就有可能导致修改丢失。如果再要解决这个问题,还可以再加上另外一个属性 @Modifying(clearAutomatically = true, flushAutomatically = true),@Modifying 的 flushAutomatically 属性为 true 时,执行 modifying query 之前会先调用 flush 操作,从而避免数据丢失问题。
  3. 在实际运行中,clear 和 flush 操作都可能需要消耗一定的时间,要根据系统实际情况可以选择使用其中的一个或两个属性,以保证系统的正确性。
User user = userDao.getOneById(1);
userDao.updateName(user.getId, 'abc');
User user2 = userDao.getOneById(1);
System.out.println(user2.getName()); //输出的是旧数据,不是abc
String name = 'abc';
User user = userDao.getOneById(1);
user.setName(name);
userDao.updateName(user.getId, name);
User user2 = userDao.getOneById(1);
System.out.println(user2.getName()); //输出的是新数据,通过set修改了缓存中的内容
@Transactional

默认情况下,repository 接口中的CRUD方法都是被@Transactional注解修饰了的。

  • 如果在同一个类中,一个非@Transaction的方法调用有@Transaction的方法不会生效,因为代理问题。
  • 在类上相当于在每个public方法上加上@Transaction。
  1. 对于查sql,默认为:@Transactional(readOnly = true),即只读事务;
  2. 增删改sql,默认为:@Transactional(readOnly = false),即非只读事务;当一个事务是非只读事务的时候,我们可以进行任何操作。。
  3. 如果你需要修改repository 接口中的某些方法的事务属性,可以在该方法上重新加上@Transactional注解,并设置需要的属性。

4)Native SQL Query

注意:

  1. 接收数据的实体必须添加:@Entity @Id的注释,属性采用驼峰并对应sql是下划线。
    如果jpa开启自动建表的时候,会在数据库生成一些数据为空的表。
  2. 如果不拿实体接收数据,query.getResultList()默认返回List<Object[]>

1>查询

StringBuffer sql = new StringBuffer();
sql.append("select * from user where id = :id");
Map<String, Object> map = new HashMap<>();
map.put("id", id);

Query query = em.createNativeQuery(sql.toString(), User.class);
//通过setParameter注入参数,有效预防sql注入
map.forEach(query::setParameter);
//UserVo必须添加@Entity注解(配置了Java类与数据库映射)。会在数据库自动建表,可以关闭jpa自动建表功能
List<UserVo> users = query.getResultList();

数量查询:

sql = "select count(1) from..."
Query query = em.createNativeQuery(sql.toString());
Object count = query.getSingleResult();
return Long.valueOf(count);

2>更新

StringBuilder sb = new StringBuilder();
sb.append("update user set name = :name'")
            .append(……)Query query = em.createNativeQuery(sb.toString());
map.forEach(query::setParameter);
query.executeUpdate();

3>分页查询

Query query = em.createNativeQuery(sql.toString(), User.class).setParameter(1, "aaa");
query.setFirstResult(page * size);
query.setMaxResults(size);
list = query.getResultList();

4>批量更新

JdbcTemplate语法:batchUpdate

5)Pageable分页查询

import org.springframework.data.domain.Pageable;
@Query(value = "SELECT* FROM stu_tab ",nativeQuery = true)
    List<ThreatEntity> getThreatTrend_3h(Pageable pageable);
@RequestMapping(value = "/testPageable", method = RequestMethod.GET)
public Page<User> testPageable(
        @RequestParam("page") Integer page,  //页码
        @RequestParam("size") Integer size,    //每页数量 
        @RequestParam("sortType") String sortType,  //排序方式:ASC/DESC
        @RequestParam("sortableFields") String sortableFields  //排序字段
) {
    //判断排序类型及排序字段  
    Sort sort = "ASC".equals(sortType) ? Sort.by(Sort.Direction.ASC, sortableFields) : Sort.by(Sort.Direction.DESC, sortableFields);
    //获取pageable
    Pageable pageable = new PageRequest(page-1,size,sort);
    return userRepository.findAll(pageable);
}

踩坑:sql语句中有distinct但无order by。加入pageable后报错如下:

SELECT DISTINCT ON expressions must match initial ORDER BY expressions

原因是:
1,在sql中当order by和distinct同时使用时,如果指定了 SELECT DISTINCT,那么 ORDER BY 子句中的项就必须出现在选择列表中。
2,在sql中加入order by语句即可解决该问题。
3,pageable 中 的sort会自动在sql后面拼接order by语句。

6)批量插入删除

jpa的批量插入效率较低,在要求高性能批量插入的情况下可以使用spring jdbc进行批量插入,可以明显提升插入效率。
同理,jpa中自带的deleteBy…方法性能很差,遍历所有,根据id删除,所以需要自己写本地sql进行删除。

jpa批量插入慢原因

jpa插入源码:

@Transactional
    public <S extends T> List<S> saveAll(Iterable<S> entities) {

        Assert.notNull(entities, "The given Iterable of entities not be null!");

        List<S> result = new ArrayList<S>();

        for (S entity : entities) {
            result.add(save(entity));
        }

        return result;
    }
@Transactional
    public <S extends T> S save(S entity) {

        if (entityInformation.isNew(entity)) {
            em.persist(entity);
            return entity;
        } else {
            return em.merge(entity);
        }
    }

查看源码可以知道批量插入saveAll 循环对每个对象进行save操作,每次save时会对对象做存在性检查,就是先查一遍,要是不存在才会保存。

7)inet(IPv4 或者 IPv6 网络地址),jsonb

1>UserType和方言

JPA不能识别pg的一些类型:inet,jsonb。为了自动转换需要开发者自定义一些类型,称为方言。

  1. 为了让JPA支持jsonb,需要自定义PostgreSQL9Dialect
import org.hibernate.dialect.PostgreSQL9Dialect;

import java.sql.Types;

public class CustomPostgreSqlDialect extends PostgreSQL9Dialect {

    public CustomPostgreSqlDialect() {
        super();
        this.registerColumnType(Types.JAVA_OBJECT, "jsonb");
    }
}

  1. 指定方言spring.jpa.database-platform: com.xxx.PostgreSqlDialect,如下
spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/postgres
    username: postgres
    password: 123456
    hikari:
      connection-timeout: 20000
      maximum-pool-size: 5
  jpa:
    # 指定数据库管理系统,可省略
    database: MYSQL
    hibernate:
      # 自动建表
      ddl-auto: create
      #命名策略
      naming:
        strategy: org.hibernate.cfg.ImprovedNamingStrategy
      database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    #打印sql
    show-sql: true 
    database-platform: com.example.jsonb.config.CustomPostgreSqlDialect
    properties:
      hibernate.temp.use_jdbc_metadata_defaults: false
logging:
  level:
    # 打印日志
    org.hibernate.type.descriptor.sql.BasicBinder: trace
    org.springframework.security:
      - debug
      - info
    org.springframework.web: error
    # 打印sql参数
    org.hibernate.SQL: debug
    org.hibernate.engine.QueryParameters: debug
    org.hibernate.engine.query.HQLQueryPlan: debug
    org.hibernate.type.descriptor.sql.BasicBinder: trace 

  1. 自定义jsonb数据类型
public class JsonbMapType<T> implements UserType {}
public class JsonbListType<T> implements UserType {
public class InetType<T> implements UserType {}
  1. 然后就可以在PO中使用这种类型啦。

2>Po定义

@Entity
@TypeDefs({@TypeDef(name = "JsonbMapType", typeClass = JsonbMapType.class),
        @TypeDef(name = "JsonbListType", typeClass = JsonbListType.class),
        @TypeDef(name = "InetType", typeClass = InetType.class)})
@Table(name = "test_table")
@Data
public class TestObjcetPo implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Type(type = "InetType")
    @Column(name = "ip", columnDefinition = "inet")
    private String ip;

    @Column(name = "test_objcet1s")
    @Type(type = "JsonbListType")
    private List<TestObject1> testObject1s;

    @Temporal(TemporalType.TIMESTAMP)
    @CreationTimestamp  #加了这个注解会自动更新创建时间
    @Column(name = "create_time")
    private Date createTime;

    @Temporal(TemporalType.TIMESTAMP)
    @UpdateTimestamp #加了这个注解会自动更新更新时间
    @Column(name = "update_time")
    private Date updateTime;

}

3>sql查询:host(ip)

@Query(value = "SELECT *  FROM test_table WHERE id= :id and host(ip) in (:ipList) ", nativeQuery = true)
List<TestObject> getObjectByIdAndIpList(@Param("id") long id, @Param("ipList") List<String> ipList);

4>批量操作:jdbcTemplate.batchUpdate


@Transactional(rollbackFor = Exception.class)
public int[] saveBatch(List<TestObject> list, Date nowTime) {

    return jdbcTemplate.batchUpdate("insert into test_tabkle ( update_time, soft_infos, ip) " +
                    "values ( ?, to_json(?::json), ?::inet",
            new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    ps.setObject(1, new Timestamp(nowTime.getTime()));
                    ps.setObject(2, JSON.toJSONString(list.get(i).getSoftInfos()));
                    ps.setObject(3, potentialRiskList.get(i).getIp());
                }

                @Override
                public int getBatchSize() {
                    return potentialRiskList.size();
                }
            });
}

8)jpa的更新操作-传入对象

字段有值就更新,没值就用原来的:

/**
     *复杂JPA操作  使用@Query()自定义sql语句  根据业务id UId去更新整个实体
     * 删除和更新操作,需要@Modifying和@Transactional注解的支持
     *
     * 更新操作中 如果某个字段为null则不更新,否则更新【注意符号和空格位置】
     *
     * @param huaYangArea   传入实体,分别取实体字段进行set
     * @return  更新操作返回sql作用条数
     */
    @Modifying
    @Transactional
    @Query("update HuaYangArea hy set " +
            "hy.areaName = CASE WHEN :#{#huaYangArea.areaName} IS NULL THEN hy.areaName ELSE :#{#huaYangArea.areaName} END ," +
            "hy.areaPerson = CASE WHEN :#{#huaYangArea.areaPerson} IS NULL THEN hy.areaPerson ELSE :#{#huaYangArea.areaPerson} END ," +
            "hy.updateDate = CASE WHEN :#{#huaYangArea.updateDate} IS NULL THEN hy.updateDate ELSE :#{#huaYangArea.updateDate} END ," +
            "hy.updateId =  CASE WHEN :#{#huaYangArea.updateId} IS NULL THEN hy.updateId ELSE :#{#huaYangArea.updateId} END " +
            "where hy.uid = :#{#huaYangArea.uid}")
    int update(@Param("huaYangArea") HuaYangArea huaYangArea);