Spring Data JPA 核心笔记
Spring Data Jpa 笔记
Entity中常用的注解
首先我们常用的注解包括(@Entity、@Table、@Id、@IdClass、@GeneratedValue、@Basic、@Transient、@Column、@Temporal、@Enumerated、@Lob)
- @Entity使用此注解定义的对象将会成为被JPA管理的实体,将映射到指定的数据库表@Entity(name = "user")其中name默认是此实体类的名字,全局唯一。
- @Table指定此实体类对应的数据库的表名。若注解不加名字则系统认为表名和实体类的名字相同
- @Id定义字段为数据库的主键,一个实体里面必须有一个。
-
@IdClass利用外部类的联合主键,其中外部类必须满足一下几点要求
必须实现Serializable接口。
必须有默认的public无参数的构造方法。
必须覆盖equals和hashCode方法。equals方法用于判断两个对象是否相同,EntityManger通过find方法来查找Entity时是根据equals的返回值来判断的。hashCode方法返回当前对象的哈希码,生成的hashCode相同的概率越小越好,算法可以进行优化。 -
@GeneratedValue为主键生成策略
- 默认为AUTO即JPA自动选择合适的策略
- IDENTITY 适用于MySQL,策略为自增
- SEQUENCE 通过序列生成主键通过@SquenceGenerator指定序列名MySQL不支持
- TABLE 框架由表模拟产生主键,使用该策略有利于数据库移植
- @Basic表示此字段是映射到数据库,如果实体字段上没有任何注解默认为@Basic。其中可选参数为@Basic(fetch = FetchType.*LAZY*, optional = false)其中fetch默认为EAGER立即加载,LAZY为延迟加载、optional表示该字段是否可以为null
- @Transient和@Basic的作用相反,表示该字段不是一个到数据库表的字段映射,JPA映射数据库的时候忽略此字段。
-
@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 -
@Temporal用来设置Date类型的属性映射到对应精度的字段
@Temporal(TemporalType.DATE) //映射为只有日期
@Temporal(TemporalType.TIME) //映射为只有时间
@Temporal(TemporalType.TIMESTAMP) //映射为日期+时间 - @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 User{
private String firstName;
private String lastName;
}
使用And条件连接时,应这样写: findByLastNameAndFirstName(String lastName,String firstName); 条件的属性名称与个数要与参数的位置与个数一一对应
Keyword | Sample | JPQL snippet |
---|
@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)Root<T>root:代表了可以查询和操作的实体对象的根。通俗讲就是对象的属性。通过里面的Path<Y>get(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
包含
username
与
password
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注解验证参数
废话不多说,直接上表格说明:
ata-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
注解 | 作用类型 | 解释 |
---|
以上注解用到要验证参数的封装类中的属性上:
findById(findOne())和Optional<T>的使用
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<T>的使用
文档 : Optional
container对象,可能包含也可能不包含非
null
值。如果存在值,则
isPresent()
将返回
true
,
get()
将返回该值。提供依赖于是否存在包含值的其他方法,例如
orElse()
(如果值不存在则返回默认值)和
ifPresent()
(如果值存在则执行代码块)。
Optional<T> 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()){