【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 id;
	...
}

1>@Entity

  1. 声明实体类;
  2. 不可缺省。
属性说明
name默认为非限定类名,用在HQL查询中标识实体

2>@Table(name=userinfoTab)

  1. 指定数据块表映射;
  2. 可缺省,默认为类名(区分大小写)。
属性说明
name表名,缺省以类名做表名
catalog设置表所属的数据库目录
schema设置表所属的模式

3>@Id

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

4>@GeneratedValue

  1. 主键的生成策略,与@Id一起使用;
  2. 可缺省,缺省值为:strategy=GenerationType.AUTO
strategy策略说明备注
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>@DynamicInsert && @DynamicUpdate

值都是默认为true。
场景:插入、更新时动态生成sql,即sql里仅包含发生变化的字段。

3)字段注解

都可以缺省。

注解作用属性举例
@Basic属性到表字段的简单映射
@Column描述表字段信息;
注意:与数据库实际不一致不会报错,只会校验po数据是否符合数据规范。
name:字段名称,默认为字段名驼峰转下划线;
unique: 是否唯一;
nullable:可否为空;
length:长度;
insertable是否出现在insert语句中;
updatable是否出现在update语句中
@Enumerated映射枚举类型
@Type指定类型type:Hibernate类型的全限定类名;
parameters:类型所需要的参数
@Temporal映射日期与时间类型;
适用于java.util.Date和java.util.Calendar
value:要映射的内容。
DATE对应java.sql.Date,时间精确到天:2016-09-28 ;
TIME对应java.sql.Time15:50:30;
TIMESTAMP对应java.sql.Timestatmp,时间戳 2016-09-28 15:52:32:000
@Temporal(TemporalType.DATE)
@TransientORM框架将忽略该字段,不映射数据库字段。
@Lob声明字段为 Clob 或 Blob 类型。
对应pg中的oid数据类型。

1>字段自动填充(@EnableJpaAuditing)

插入、更新时自动填充字段,时传入当前时间,自动填充。
在Application启动类中添加注解 @EnableJpaAuditing

  1. @CreatedBy@PrePersist@PreUpdate
import org.springframework.data.annotation.CreatedBy;
import javax.persistence.*;

字段注解:
    /**
     * 创建人
     */
    @Column
    @CreatedBy
    private String createUser;
    
    @PrePersist
    public void prePersist() {
        createTime = LocalDateTime.now();
        createUser = HttpHelper.getCurrentUsername();
    }

    @PreUpdate
    public void preUpdate() {
        updateTime = LocalDateTime.now();
        updateUser = HttpHelper.getCurrentUsername();
    }
/**
* 创建时间
*/
@CreatedDate
private Date createTime;
/**
* 更新时间
*/
@LastModifiedDate
private Date updateTime;
/**
* 创建时间 (Hibernate)
*/
@CreationTimestamp
private Date createTime;
/**
* 更新时间 (Hibernate)
*/
@UpdateTimestamp
private Date updateTime;;
     
/**
 * 创建人
 */
@CreatedBy
private String createBy;    
/**
 * 最后修改人
 */
@LastModifiedBy
private String lastModifiedBy;


/**
 * Spring Data JPA通过AuditorAware<T>接口获取用户信息,
 * 其中泛型T可以为String保存用户名,也可以为Long/Integer保存用户ID。
 * @author EvanWang
 *
 */
@Component
public class AuditorConfig implements AuditorAware<String> {
 
    /**
     * 返回操作员标志信息
     *
     * @return
     */
    @Override
    public Optional<String> getCurrentAuditor() {
        // 这里应根据实际业务情况获取具体信息
        return Optional.of(userName);
    }
}

4)Repository接口及注解

public interface UserinfoRepository extends JpaRepository<Userinfo, Long> {
}
    @Autowired
    ThreatRepository threatRepository;
    
    threatRepository.saveAll(list);

1>内置方法

JpaRepository是Spring Data JPA提供的一个接口,它继承了PagingAndSortingRepository和CrudRepository接口,提供了一组通用的CRUD操作方法,包括增删改查等操作。

方法说明备注
save(S entity)保存或更新
findById(ID id)按主键查询
existsById(ID id)按主键判断对象是否存在
deleteById(ID id)按主键删除
findAll()查询
count()统计
findAll(Pageable pageable)分页查询
List findAll(Sort sort)排序并查询
flush()将持久化上下文中的数据强制同步到数据库
saveAll(Iterable entities)批量保存或更新性能低下,建议直接采用jspring jdbc进行批量插入
deleteInBatch(Iterable entities)批量删除
deleteAllInBatch()批量删除

2>针对属性的通用方法

可以不写sql,按约定定义方法即可:(其中,第一段…限定返回条数:TopNFirstNDistinct;第二段…表示查询的条件,通过AndOr关键词组合多个条件,用Not关键词取反)。

方法说明备注
findByXXX
findAllByXXX
findByNameAndPwdAND
findByNameOrSexOR
findByIdBetweenwhere id between ? and ?
findByIdLessThan
findByIdBefore
where id < ?
findByIdLessThanEqualwhere id <= ?
findByIdGreaterThan
findByIdAfter
where id > ?
findByIdGreaterThanEqualwhere id > = ?
findByNameIsNullwhere name is null
findByNameNotNullwhere name is not null或者isNotNull
findByNameLikewhere name like ?
findByNameNotLike
findByNameStartingWithwhere name like '?%'
findByNameEndingWithwhere name like '%?'
findByNameContainingwhere name like '%?%'
findByIdOrderByXDescOrder By
findByNameNotwhere name <> ?
findByIdIn(Collection<?> c)IN注意,in有长度限制
findByIdNotIn(Collection<?> c)
findByAaaTuewhere aaa = true
findByAaaFalse
findByNameIgnoreCasewhere UPPER(name)=UPPER(?)
count...By...统计

3>自定义SQL

  1. 查询
    当进行 find 操作时,JPA 在 EntityManager 中缓存了 find 生成的对象,当再次 find 时会直接返回该对象。
    @Query 引起的数据库变更 EntityManager 并不能发现,更进一步说,使用其它工具或者其它框架修改数据库中的数据,也不能及时反应到 JPA 的 find 系列方法上来。
    @Query(value = "select user_id from userinfo",nativeQuery = true)
    List<Long> getUserIdList();
  1. 修改
    @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);
i>@Modifying

加上@Modifying,JPA会以更新类语句来执行,而不再是以查询语句执行。 
该注解中有两个属性:
a)flushAutomatically:自动刷新,即执行完语句后立即将变化内容从缓存刷新到磁盘。
b)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 操作,从而避免数据丢失问题。
c)在实际运行中,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修改了缓存中的内容
ii>@Transactional

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

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

4>传入对象的复杂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);

5)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

6)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语句。

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();
                }
            });
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值