Spring Data JPA 核心笔记

1,584 阅读13分钟

Spring Data Jpa 笔记

Entity中常用的注解

  首先我们常用的注解包括(@Entity、@Table、@Id、@IdClass、@GeneratedValue、@Basic、@Transient、@Column、@Temporal、@Enumerated、@Lob)

  1. @Entity使用此注解定义的对象将会成为被JPA管理的实体,将映射到指定的数据库表@Entity(name = "user")其中name默认是此实体类的名字,全局唯一。

  2. @Table指定此实体类对应的数据库的表名。若注解不加名字则系统认为表名和实体类的名字相同

  3. @Id定义字段为数据库的主键,一个实体里面必须有一个。

  4. @IdClass利用外部类的联合主键,其中外部类必须满足一下几点要求

    必须实现Serializable接口。

    必须有默认的public无参数的构造方法。

    必须覆盖equals和hashCode方法。equals方法用于判断两个对象是否相同,EntityManger通过find方法来查找Entity时是根据equals的返回值来判断的。hashCode方法返回当前对象的哈希码,生成的hashCode相同的概率越小越好,算法可以进行优化。

  5. @GeneratedValue为主键生成策略

    • 默认为AUTO即JPA自动选择合适的策略
    • IDENTITY 适用于MySQL,策略为自增
    • SEQUENCE 通过序列生成主键通过@SquenceGenerator指定序列名MySQL不支持
    • TABLE 框架由表模拟产生主键,使用该策略有利于数据库移植
  6. @Basic表示此字段是映射到数据库,如果实体字段上没有任何注解默认为@Basic。其中可选参数为@Basic(fetch = FetchType.*LAZY*, optional = false)其中fetch默认为EAGER立即加载,LAZY为延迟加载、optional表示该字段是否可以为null

  7. @Transient和@Basic的作用相反,表示该字段不是一个到数据库表的字段映射,JPA映射数据库的时候忽略此字段。

  8. @Column定义实体内字段对应的数据库中的列名

    @Column(
    name = "real_name",
    unique = true, 
    nullable = false, 
    insertable = false, 
    updatable = false, 
    columnDefinition = "varchar", 
    length = 100)
    

    name对应数据库的字段名,可选默认字段名和实体属性名一样

    unique是否唯一,默认false,可选

    nullable是否允许为空。可选,默认为true

    insertable执行insert的时候是否包含此字段。可选,默认true

    updatable执行update的时候是否包含此字段。可选,默认true

    columnDefinition表示该字段在数据库中的实际类型

    length数据库字段的长度,可选,默认25

  9. @Temporal用来设置Date类型的属性映射到对应精度的字段

    @Temporal(TemporalType.DATE)    //映射为只有日期
    @Temporal(TemporalType.TIME)    //映射为只有时间
    @Temporal(TemporalType.TIMESTAMP)  //映射为日期+时间
    
  10. @Lob将字段映射成数据库支持的大对象类型,支持一下两种数据库类型的字段。(注意:Clob、Blob占用的内存空间较大,一般配合@Basic(fetch = FetchType.*LAZY*)将其设置为延迟加载)

    • Clob:字段类型为Character[]、char[]、String将被映射为Clob
    • Blob:字段类型为Byte[]、byte[]和实现了Serializable接口的类型将被映射为Blob类型

JPA 常用类

基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下:

Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类

CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法

PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法

JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法

自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。

JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法

JPA 方法定义规范

简单条件查询: 查询某一个实体类或者集合

按照 Spring Data 的规范,查询方法以 find | read | get 开头, 涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写

例如:定义一个 Entity 实体类

 class Userprivate String firstName; 
  private String lastName; 
 } 

使用And条件连接时,应这样写: findByLastNameAndFirstName(String lastName,String firstName); 条件的属性名称与个数要与参数的位置与个数一一对应

KeywordSampleJPQL snippet
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstname,
findByFirstnameIs,
findByFirstnameEquals
… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1(parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1(parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1(parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection<Age> ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection<Age> age)… where x.age not in ?1
TruefindByActiveTrue()… where x.active = true
FalsefindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)
CountcountByFirstNameselect count(*) from ... where x.firstName = ?1
ExistsexistsByFirstNamelike the dao.exists(Example),judge by attribution of firstName:select keyindex0_.id as col_0_0_ from key_index keyindex0_ where keyindex0_.name=? limit ?

@Query 注解

实体声明查询,默认 SQL 可写成从映射对象查询,若加入 nativeQuery = true ,则直接写原生 SQL

    // 根据层次编号查询模板
    @Query(value = "select  mb from Wsscmb mb where mb.ccbh = :ccbh")
    Wsscmb getByCcbh(String ccbh);

命名参数(推荐使用这种方式):

定义好参数名,赋值时采用 @Param ("参数名"),不用管顺序。

    // 根据联合主键修改模板的文书生成类别
    @Query(nativeQuery = true, value = "update S_WSSCMB set WSSCLB = :wssclb where COURT_NO = :courtno and XH = :xh")
    @Modifying
    int modifyWssclb(@Param("courtno") String courtno, @Param("xh") Integer xh, @Param("wssclb") String wssclb);

如果是 @Query 中有 LIKE 关键字,需要在占位符加 %,也可传入拼接好的变量

   @Query("select o from UserModel o where o.name like ?1%")
   public List<UserModel> findByUuidOrAge(String name);

  @Query("select o from UserModel o where o.name like %?1")
  public List<UserModel> findByUuidOrAge(String name);

  @Query("select o from UserModel o where o.name like %?1%")
  public List<UserModel> findByUuidOrAge(String name);

@Modifying 注解和事务

@Query 与 @Modifying 这两个注解一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最为常用,示例如下:

	@Query(nativeQuery = true, value = "update S_WSSCMB set WSSCLB = :wssclb where COURT_NO = :courtno and XH = :xh")
	@Modifying
	int modifyWssclb(@Param("courtno") String courtno, @Param("xh") Integer xh, @Param("wssclb") String wssclb);

在调用该 数据库访问方法的 servie 的实现类加上事务注解

	@Transactional
	@Override
	public Integer modifyWssclb(String courtno, Integer xh, String wssclb) {
		int rows = wsscmbDao.modifyWssclb(courtno, xh, wssclb);
		return rows;
	}

CrudRepository

CrudRepository 接口提供了最基本的对实体类的添删改查操作

 T save(T entity);//保存单个实体 

 Iterable<T> save(Iterable<? extends T> entities);//保存集合    

 T findOne(ID id);//根据id查找实体    

 boolean exists(ID id);//根据id判断实体是否存在    

 Iterable<T> findAll();//查询所有实体,不用或慎用!    

 long count();//查询实体数量    

 void delete(ID id);//根据Id删除实体    

 void delete(T entity);//删除一个实体 

 void delete(Iterable<? extends T> entities);//删除一个实体的集合    

 void deleteAll();//删除所有实体,不用或慎用! 

PagingAndSortingRepository

该接口提供了分页与排序功能

 Iterable<T> findAll(Sort sort); //排序 

 Page<T> findAll(Pageable pageable); //分页查询(含排序功能) 
@Test
	public void testPagingAndSortingRespository(){
		//pageNo 从 0 开始. 
		int pageNo = 6 - 1;
		int pageSize = 5;
		//Pageable 接口通常使用的其 PageRequest 实现类. 其中封装了需要分页的信息
		//排序相关的. Sort 封装了排序的信息
		//Order 是具体针对于某一个属性进行升序还是降序. 
		Order order1 = new Order(Direction.DESC, "id");
		Order order2 = new Order(Direction.ASC, "email");
		Sort sort = new Sort(order1, order2);
		
		PageRequest pageable = new PageRequest(pageNo, pageSize, sort);
		Page<Person> page = personRepsotory.findAll(pageable);
		
		System.out.println("总记录数: " + page.getTotalElements());
		System.out.println("当前第几页: " + (page.getNumber() + 1));
		System.out.println("总页数: " + page.getTotalPages());
		System.out.println("当前页面的 List: " + page.getContent());
		System.out.println("当前页面的记录数: " + page.getNumberOfElements());
	}

JpaRepository

该接口提供了JPA的相关功能

List<T> findAll(); //查找所有实体 
List<T> findAll(Sort sort); //排序、查找所有实体 
List<T> save(Iterable<? extends T> entities);//保存集合 
void flush();//执行缓存与数据库同步 
T saveAndFlush(T entity);//强制执行持久化 
void deleteInBatch(Iterable<T> entities);//删除一个实体集合 

JpaSpecificationExecutor

不属于Repository体系,实现一组 JPA Criteria 查询相关的方法

接收参数Specifcation对象该对象是一个接口,此对象的作用想当与在hibernateJPA中使用QBC查询的操作,只是使用了Specifcation对象进行了封装

可以看出JpaSpecificationExecutor接口基本是围绕Specification接口定义的。而Specification接口最常用的就是以下方法:

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);

(1)Rootroot:代表了可以查询和操作的实体对象的根。通俗讲就是对象的属性。通过里面的Pathget(String attributeName)来获得我们操作的字段

(2)CriteriaQuery<?>query:代表一个specific的顶层查询对象,它包含着查询的各个部分,比如:select、from、where、group by、order by等。它提供了查询ROOT的方法。常用的方法有

package javax.persistence.criteria;

import java.util.List;
import java.util.Set;

public interface CriteriaQuery<T> extends AbstractQuery<T> {

    CriteriaQuery<T> select(Selection<? extends T> selection);

    CriteriaQuery<T> multiselect(Selection<?>... selections);

    CriteriaQuery<T> multiselect(List<Selection<?>> selectionList);

    CriteriaQuery<T> where(Expression<Boolean> restriction);

    CriteriaQuery<T> where(Predicate... restrictions);

    CriteriaQuery<T> groupBy(Expression<?>... grouping);

    CriteriaQuery<T> groupBy(List<Expression<?>> grouping);

    CriteriaQuery<T> having(Expression<Boolean> restriction);

    CriteriaQuery<T> having(Predicate... restrictions);

    CriteriaQuery<T> orderBy(Order... o);

    CriteriaQuery<T> orderBy(List<Order> o);

    CriteriaQuery<T> distinct(boolean distinct);

    List<Order> getOrderList();

    Set<ParameterExpression<?>> getParameters();
}

(3)CriteriaBuilder cb:用来构建CritiaQuery的构建器对象,其实就相当于条件或者是条件组合,即以Predicate的形式返回。构建简单的Predicate示例:

Predicate predicate = criteriaBuilder.like(root.get("name"),name);

例子

/**
	 * 目标: 实现带查询条件的分页. id > 5 的条件
	 * 
	 * 调用 JpaSpecificationExecutor 的 Page<T> findAll(Specification<T> spec, Pageable pageable);
	 * Specification: 封装了 JPA Criteria 查询的查询条件
	 * Pageable: 封装了请求分页的信息: 例如 pageNo, pageSize, Sort
	 */
	@Test
	public void testJpaSpecificationExecutor(){
		int pageNo = 3 - 1;
		int pageSize = 5;
            PageRequest pageable = PageRequest.of(pageNo, pageSize);
		
		//通常使用 Specification 的匿名内部类
		Specification<Person> specification = new Specification<Person>() {
        /**
        * @param root: 代表查询的实体类. 
        * @param query: 可以从中可到 Root 对象, 
        * 即告知 JPA Criteria 查询要查询哪一个实体类. 添加查询条件
        * @param cb: CriteriaBuilder 对象. 创建 Criteria 相关对象的工厂.
        * 当然可以从中获取到 Predicate 对象
        * @return: Predicate 类型, 代表一个查询条件. 
        */
			@Override
			public Predicate toPredicate(Root<Person> root,
					CriteriaQuery<?> query, CriteriaBuilder cb) {
				Path path = root.get("id");
				Predicate predicate = cb.gt(path, 5);
				return predicate;
			}
		};
		
		Page<Person> page = personRepsotory.findAll(specification, pageable);
		
		System.out.println("总记录数: " + page.getTotalElements());
		System.out.println("当前第几页: " + (page.getNumber() + 1));
		System.out.println("总页数: " + page.getTotalPages());
		System.out.println("当前页面的 List: " + page.getContent());
		System.out.println("当前页面的记录数: " + page.getNumberOfElements());
	}

单条件查询

    Specification spc = new Specification(){
        @Override
        /**
         * @param root 根参数,通过次对象获取实体对象字段进行条件的设置
         * @param criteriaQuery 定义一个基本的查询
         * @param criteriaBuilder 创建一个查询条件
         * @return: javax.persistence.criteria.Predicate
         */
        public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
            return criteriaBuilder.equal(root.get("username"),"AAA");
        }
    };

多条件查询

直接在条件查询内写入查询的条件即可SpringData 会自动的将条件进行拼接

    Specification spc = (x,y,z)->z.and(z.equal(x.get("username"),"AAA"),
                                       z.equal(x.get("password"),"123"));
    List<Users> list = this.userDaoFive.findAll(spc);
    list.stream().forEach(System.out::println);

分页查询

    Specification<Users> spc = (x,y,z)->z.ge(x.get("userid"),2);
    Page<Users> page = this.userDaoFive.findAll(spc, new PageRequest(0, 5));
    page.getContent().stream().forEach(System.out::println);

排序查询

	Specification<Users> spc = (x,y,z)->z.ge(x.get("userid"),2);
    List<Users> list = 
    this.userDaoFive.findAll(spc, new Sort(Sort.Direction.DESC,"userid"));
    list.stream().forEach(System.out::println);

多条件排序

    List< Order> orders=new ArrayList< Order>();
    orders.add( new Order(Direction. ASC, "c"));
    orders.add( new Order(Direction. DESC, "d"));
    Pageable pageable= new PageRequest(pageNumber, pageSize, new Sort(orders));
    jpaRepo.findByAAndB(a,b,pageable);

分页+排序+条件

	Specification<Users> spc = (x,y,z)->z.ge(x.get("userid"),2);
    Page<Users> page = this.userDaoFive
    .findAll(spc, new PageRequest(0, 3, new Sort(Sort.Direction.DESC, "userid")));
    page.getContent().stream().forEach(System.out::println);

Spring Data JPA 注意点

  • @id@GeneratedValue(strategy = GenerationType.IDENTITY) 策略需要设置表id主键自增

  • @Entity 默认 UserInfo 表会映射数据库中的 user_info 表,相关错误 Unknown column 'user0_.create_time' in 'field list'

  • Table 'dev-test.hibernate_sequence' doesn't exist 一般是实体对象的主键生成策略设置问题

  • Unknown column in field list 表示实体对象中的字段与数据库不对应

  • jpa-hibernate-ddl-auto:update 配置自动更新或创建数据表

  • @Transient 表示该属性并非一个到数据库表的字段的映射,表示非持久化属性

补充

Iterable类型

  • java.lang.Iterable
  • java.util.Iterator
  • Iterator是迭代器类,而Iterable是接口。

好多类都实现了Iterable接口,这样对象就可以调用iterator()方法。

Jpa 中 Iterable<> 返回类型的值与 List<> 类型转换的两种方式

Iterable<Entity> geted = entityDao.findAll();
List<Entity> list = Lists.newArrays();
geted.forEach(single ->{list.add(single)});
import com.google.common.collect.Lists;

 @RequestMapping("/findAll")
    public Iterable<Person> findAll(){
        List<Person> people = Lists.newArrayList(demoService.findAll());
        return demoService.findAll();
    }

数据库连接配置

常用数据库连接配置

spring.datasource.url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&&serverTimezone=UTC
spring.datasource.username = 
spring.datasource.password = 

联合主键实体类配置

关键点

  • 联合主键 id 属性加入 @EmbeddedId
  • 联合主键类名前加入@Embeddable 并实现有参无参构造方法
  • 可使用 Lombok 注解 @NoArgsConstructor @AllArgsConstructor
@Entity
@Table(name = "S_WSSCMB")
@Data
@Accessors(chain = true)
@ApiModel("文书生成模板")
public class Wsscmb implements Serializable {

	private static final long serialVersionUID = -9069767536642372411L;

	/** 联合主键 */
	@EmbeddedId
	@NotNull
	@Valid
	@ApiModelProperty("联合主键")
	private WsscmbPK id;
}
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("S_WSSCMB 表联合主键")
@Embeddable
public class WsscmbPK implements Serializable {

	private static final long serialVersionUID = -6264471737217641392L;

	/** 法院编号 */
	@Column(name = "{COURT_NO}", length = 4, nullable = false)
	@NotBlank
	@Length(max = 4)
	@JsonView(Wsscmb.SimpleView.class)
	@ApiModelProperty("法院编号")
	private String courtNo;

	/** 序号 */
	@Column(name = "{XH}", nullable = false)
	@NotNull(groups = UpdateGroup.class)
	@JsonView(Wsscmb.SimpleView.class)
	@ApiModelProperty("序号")
	private Integer xh;

}

@JsonView 使用

前端查询获取后端传递的对象数据,后端进行查询时会有冗余数据,可采用 @JsonView 指定返回的对象数据字段

用法

  • 首先在数据库映射实体对象里新建两个接口
    public interface SimpleView {};  
    public interface DetailView extends SimpleView{}; //继承
  • 然后用 @JsonView 在类的属性上指定视图包含字段
 @JsonView(UserSimpleView.class)
 private String username;
 
 @JsonView(UserDetailView.class)
  private String password;
  • Controller 指定返回数据使用的映射类的视图
    @GetMapping("query1")
    @JsonView(User.SimpleView.class)
    public List<User> query(){...}

    @GetMapping("query1")
    @JsonView(User.SimpleView.class)
    public List<User> query(){...}

此时 query1 返回的数据对象只包含 username 字段,而 query2 包含 usernamepassword

Lombok 常用注解

  • @Setter 注解在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
  • @Getter 使用方法同上,区别在于生成的是getter方法。
  • @ToString 注解在类,添加toString方法。
  • @EqualsAndHashCode 注解在类,生成hashCode和equals方法。
  • @NoArgsConstructor 注解在类,生成无参的构造方法。
  • @RequiredArgsConstructor 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
  • @AllArgsConstructor 注解在类,生成包含类中所有字段的构造方法。
  • @Data 注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
  • @Slf4j 注解在类,生成log变量,严格意义来说是常量。private static final Logger log = LoggerFactory.getLogger(UserController.class);

Entity 中建立索引

@Entity
@Table(
    name = "{SPWF_FLOW}",
	indexes = {
		@Index(name = "IDX_TYPE_ZT_XH_ACT", 
               columnList = "{WF_FLOWTYPE_ID},{ZT},{XH},{WF_FLOWVER_ID_ACT}")
	}
)

SpringBoot注解验证参数

废话不多说,直接上表格说明:

注解作用类型解释
@NotNull任何类型属性不能为null
@NotEmpty集合集合不能为null,且size大于0
@NotBlanck字符串、字符字符类不能为null,且去掉空格之后长度大于0
@AssertTrueBoolean、boolean布尔属性必须是true
@Min数字类型(原子和包装)限定数字的最小值(整型)
@Max同@Min限定数字的最大值(整型)
@DecimalMin同@Min限定数字的最小值(字符串,可以是小数)
@DecimalMax同@Min限定数字的最大值(字符串,可以是小数)
@Range数字类型(原子和包装)限定数字范围(长整型)
@Length字符串限定字符串长度
@Size集合限定集合大小
@Past时间、日期必须是一个过去的时间或日期
@Future时期、时间必须是一个未来的时间或日期
@Email字符串必须是一个邮箱格式
@Pattern字符串、字符正则匹配字符串

以上注解用到要验证参数的封装类中的属性上:

findById(findOne())和Optional的使用

1、findOne()方法的替代方法findById()

2.0版本,Spring-Data-Jpa修改findOne()。

1)2.0版本之前

T findOne(ID primaryKey);

2)2.0版本

  Optional<T> findById(ID id); 

2、Optional Optional的使用

文档Optional

container对象,可能包含也可能不包含非null值。如果存在值,则isPresent()将返回trueget()将返回该值。提供依赖于是否存在包含值的其他方法,例如orElse()(如果值不存在则返回默认值)和ifPresent()(如果值存在则执行代码块)。 Optional findById(ID id)中Optional的一些用法

1)如果没找到指定实体,则返回一个默认值。

Foo foo = repository.findById(id)
                    .orElse(new Foo());

或者

Foo foo = repository.findById(id)
                    .orElse(null);

2)如果找到实体返回它,否则抛出异常

return repository.findById(id)
        .orElseThrow(() -> new EntityNotFoundException(id));

3)假设希望根据是否找到实体来应用不同的处理(无需抛出异常)

Optional<Foo> fooOptional = fooRepository.findById(id);
if (fooOptional.isPresent()){
    Foo foo = fooOptional.get();
   // 处理 foo ...
}
else{
   //另一种情况....
}

Jpa 错误收集

  • Could not commit JPA transaction RollbackException: Transaction marked as rollbackOnly可能存在 实体类类型错误或没有通过@Notnull 注解