SpringBoot整合JPA
JPA使用步骤
- 导入依赖
- 编写jpa配置
- 编写实体类,并添加JPA相关注解,用于实现实体类与数据表的关联
- 编写dao层,创建数据访问接口
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--lombok 注解插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
2、修改application.properties文件
# 配置数据源
spring.datasource.url=jdbc:mysql://localhost:3306/sjk?
serverTimezone= Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=java
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
3、JPA实体类注解
@Entity
@Entity标记在类名上面,作为实体类的标识
@Table
- 当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,该标注与 @Entity 标注并列使用,置于实体类声明语句之前,可写于单独语句行,也可与声明语句同行。
- @Table 标注的常用选项是 name,用于指明数据库的表名
- @Table标注还有一个两个选项 catalog 和 schema 用于设置表所属的数据库目录或模式,通常为数据库名。uniqueConstraints选项用于设置约束条件,通常不须设置。
@Id
- @Id设置对象表示符,标识的实体类的属性映射对应表中的主键
@GeneratedValue
设置标识符的生成策略,常与@Id一起使用
参数:strategy指定具体的生成策略
- 方式一:@GeneratedValue(strategy=GenerationType.AUTO) 也是默认策略, 即写成@GeneratedValue也可;
类似于hibernate的native策略,生成方式取决于底层的数据库。 - 方式二:@GeneratedValue(strategy = GenerationType.IDENTITY)指定“自动增长”策略,适用于MySQL;
- 方式三:@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = “seq_tbl_person”)指定“序列”策略,常用于Oracle,其中generator表示生成器的名字。而且还要指定@SequenceGenerator(name = “seq_tbl_person”, sequenceName = “seq_tbl_person”, allocationSize = 1)注解配合使用
其中name指定生成器的名字(与generator的值一样),sequenceName指定数据库中定义序列的名字,allocationSize指定序列每次增长1
@Column
描述数据库表中该字段的定义,具有一下属性
- name:表示数据库表中该字段的名称,默认情形属性名称一致。
- nullable:表示该字段是否允许为null,默认为true。
- unique:表示该字段是否是唯一标识,默认为false。
- length:表示该字段的大小,仅对String类型的字段有效。
- insertable:表示在ORM框架执行插入操作时,该字段是否应出现INSETRT语句中,默认为true。
- updateable:表示在ORM框架执行更新操作时,该字段是否应该出现在UPDATE语句中,默认为true。对于一经创建就不可以更改的字段,该属性非常有用,如对于birthday字段。
- columnDefinition:表示该字段在数据库中的实际类型。通常ORM框架可以根据属性类型自动判断数据库中字段的类型,但是对于Date类型仍无法确定数据库中字段类型究竟是DATE,TIME还是TIMESTAMP。此外,String的默认映射类型为VARCHAR,如果要将String类型映射到特定数据库的BLOB或TEXT字段类型,该属性非常有用。
@OrderBy
在加载数据的时候可以为其指定顺序。
@Transient
表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性。如果一个属性并非数据库表的字段映射。就务必将其标示为@Transient。否则。ORM框架默认其注解为@Basic
@OneToOne
描述一个一对一的关联
可选
- fetch:表示抓取策略,默认为FetchType.LAZY
- cascade:表示级联操作策略
@ManyToOne
表示一个多对一的映射,该注解标注的属性通常是数据库表的外键
- optional:是否允许该字段为null,该属性应该根据数据库表的外键约束来确定,默认为true
可选 - fetch:表示抓取策略,默认为FetchType.EAGER
- cascade:表示默认的级联操作策略,可以指定为ALL,PERSIST,MERGE,REFRESH和REMOVE中的若干组合,默认为无级联操作
- targetEntity:表示该属性关联的实体类型。该属性通常不必指定,ORM框架根据属性类型自动判断targetEntity。
@OneToMany
描述一个一对多的关联,该属性应该为集体类型,在数据库中并没有实际字段。
- fetch:表示抓取策略,默认为FetchType.LAZY,因为关联的多个对象通常不必从数据库预先读取到内存
可选 - cascade:表示级联操作策略,对于OneToMany类型的关联非常重要,通常该实体更新或删除时,其关联的实体也应当被更新或删除
例如:实体User和Order是OneToMany的关系,则实体User被删除时,其关联的实体Order也应该被全部删除
@ManyToMany
描述一个多对多的关联.多对多关联上是两个一对多关联,但是在ManyToMany描述中,中间表是由ORM框架自动处理
可选
- targetEntity:表示多对多关联的另一个实体类的全名,例如:package.Book.class
- mappedBy:表示多对多关联的另一个实体类的对应集合属性名称
两个实体间相互关联的属性必须标记为@ManyToMany,并相互指定targetEntity属性,
需要注意的是,有且只有一个实体的@ManyToMany注解需要指定mappedBy属性,指向targetEntity的集合属性名称
利用ORM工具自动生成的表除了User和Book表外,还自动生成了一个User_Book表,用于实现多对多关联
@JoinColumn
可选
@JoinColumn和@Column类似,介量描述的不是一个简单字段,而一一个关联字段,例如.描述一个@ManyToOne的字段.
- name:该字段的名称.由于@JoinColumn描述的是一个关联字段,如ManyToOne,则默认的名称由其关联的实体决定.例如,实体Order有一个user属性来关联实体User,则Order的user属性为一个外键,
其默认的名称为实体User的名称+下划线+实体User的主键名称
@JoinTable(name = “student_teacher”, inverseJoinColumns = @JoinColumn(name = “tid”), joinColumns = @JoinColumn(name = “sid”))
可选:由第三张表来维护两张表的关系
name:是关系表的名字
joinColumns:自己这一端的主键
inverseJoinColumns:对方的主键
@MappedSuperclass
可选@MappedSuperclass可以将超类的JPA注解传递给子类,使子类能够继承超类的JPA注解
@Embedded
@Embedded将几个字段组合成一个类,并作为整个Entity的一个属性.
例如User包括id,name,city,street,zip属性.
我们希望city,street,zip属性映射为Address对象.这样,User对象将具有id,name和address这三个属性.
Address对象必须定义为@Embededable
3.1、实体类注解实例
@Entity
@Table(name = "aoa_user")
public class User {
@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId; //用户id
@Column(name = "user_name")
@NotEmpty(message = "用户名不能为空")
private String userName; //登录用户名
@Column(name = "user_tel")
@NotEmpty(message = "电话不能为空")
private String userTel; //用户电话
@Column(name = "real_name")
@NotEmpty(message = "真实姓名不能为空")
private String realName; //真实姓名
private String pinyin;
@NotEmpty(message = "邮箱不能为空")
// @Pattern(regexp = "^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\\.[a-zA-Z0-9_-]{2,3}){1,2})$", message =
// "请填写正确邮箱号")
private String eamil; //邮件
@NotEmpty(message = "地址不能为空")
private String address; //地址
@Column(name = "user_edu")
@NotEmpty(message = "学历不能为空")
private String userEdu; //用户学历
private Boolean superman = false;
@Column(name = "user_school")
@NotEmpty(message = "毕业院校不能为空")
private String school; //学校
@Column(name = "user_idcard")
// @Pattern(regexp = "^(\\d{6})(19|20)(\\d{2})(1[0-2]|0[1-9])(0[1-9]|[1-2][0-9]|3[0-1])(\\d{3})(\\d|X|x)" +
// "?$", message = "请填写正确身份证号")
private String idCard; //用户身份证
@NotEmpty(message = "卡号不能为空")
// @Length(min = 16, max = 19, message = "银行卡号长度必须在16到19之间!")
private String bank; //银行
private String sex; //性别
@Column(name = "theme_skin")
private String themeSkin; //主题皮肤
private Date birth; //生日
@Column(name = "user_sign")
private String userSign; //用户签名
private String password; //用户密码
private String salary; //用户薪水
@Column(name = "img_path")
private String imgPath; //用户头像路径
@Column(name = "hire_time")
private Date hireTime; //入职时间
@Column(name = "is_lock")
private Integer isLock = 0; //该用户是否被禁用
@Column(name = "last_login_ip")
private String lastLoginIp; //用户最后登录ip;
@Column(name = "last_login_time")
private Date lastLoginTime; //最后登录时间
@Column(name = "modify_time")
private Date modifyTime; //最后修改时间
@Column(name = "modify_user_id")
private Long modifyUserId; //最后修改此用户的用户id
@Column(name = "father_id")
private Long fatherId; //上司id
private Integer holiday; //请假天数
@ManyToOne()
@JoinColumn(name = "position_id")
private Position position; //外键关联 职位表
@ManyToOne()
@JoinColumn(name = "dept_id")
private Dept dept; //外键关联 部门表
@ManyToOne()
@JoinColumn(name = "role_id")
private Role role; //外键关联 角色表
@ManyToMany(mappedBy = "users")
private List<ScheduleList> scheduleLists;
@ManyToMany(mappedBy = "users")
private List<Reply> replys;
@ManyToMany(mappedBy = "users")
private List<Discuss> discuss;
@ManyToMany(mappedBy = "userss")
private List<Note> note;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Attends> aSet;
// setter getter 方法略
.......
}
4、创建数据访问接口
继承JpaRepository类,或者是PagingAndSortingRepository类,在这两个类对象中有着一些公共的操作数据库的方法,继承后,可以直接进行使用。
public interface orderMasterRepository extends JpaRepository<表名, 表的主键字段类型> {}
public interface PositionDao extends PagingAndSortingRepository<Position, Long>{}
// PagingAndSortingRepository继承自CrudRepository接口,所以除了拥有CrudRepository的功能之外,还增加了排序和分页查询的功能。
4.1、JpaRepository
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAllById(Iterable<ID> var1);
<S extends T> List<S> saveAll(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
4.2、PagingAndSortingRepository
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort var1);
Page<T> findAll(Pageable var1);
}
4.3、CrudRepository
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S var1);
<S extends T> Iterable<S> saveAll(Iterable<S> var1);
Optional<T> findById(ID var1);
boolean existsById(ID var1);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> var1);
long count();
void deleteById(ID var1);
void delete(T var1);
void deleteAll(Iterable<? extends T> var1);
void deleteAll();
}
4.3、JPA常用的公共方法
(1) list<?> findAll() // 查询所有数据,返回list对象
List<OrderDetail> orderDetailList = orderDetailRepository.findAll();
(2) save(): // 添加数据,返回插入成功的对象
orderMasterRepository.save(orderMaster);
(3)saveAndFlush(T) // 保存与更新,返回更新或者插入成功的对象
productCategoryRepository.saveAndFlush(productCategory);
(4)findById(主键id) // 返回Optional对象
productCategoryRepository.findById(1).get();
Optional对象方法,查询成功使用get返回定义实体类对象,否则返回null
(5)deleteById(主键ID) // 根据主键id删除对象信息,没有返回值
orderDetailRepository.deleteById("1");
(6)count() // 统计对象的记录条数(不怎么常用),返回long型数据
Long l = orderDetailRepository.count();
5、分页查询
jpa已经帮我们实现了分页的功能(直接使用即可),在查询的方法中,需要传入参数Pageable
,当查询中有多个参数的时候Pageable建议做为最后一个参数传入。
Page<User> findAll(Pageable pageable);
// 传入Pageable对象后,jpa会自动根据此对象进行数据的分页显示
例
// 按照单个条件排序,并分页查询
@GetMapping("/getPage")
public Page<Stucls> PageQuery() throws Exception {
Sort sort = Sort.by(Sort.Direction.ASC, "clsBh");
// 构建Pageable对象
Pageable pageable = PageRequest.of(1,2,sort);
// 根据传入的Pageable对象进行分页
return stuCls.findAll(pageable);
}
// 按照多个指定条件排序,并且进行分页查询
@GetMapping("/getPageAll")
public Page<Stucls> PageQuery() throws Exception {
List<Order> orders = new ArrayList<>();
// 添加多个排序条件
orders.add(new Order(Direction.DESC, "clsBh"));
orders.add(new Order(Direction.DESC, "clsName"));
....
// 将集合放入Sort排序对象中
Sort sort = Sort.by(orders);
// 构建Pageable对象
Pageable pageable = PageRequest.of(1,2,sort);
// 根据传入的Pageable对象进行分页
return stuCls.findAll(pageable);
}
Page对象常用方法
// Page是一个泛型接口,代表查询的单页结果集,也有其他信息。常用以下方法
int getTotalPages() //返回总的页数
long getTotalElements() //返回总行数
List<T> getContent() //返回查询结果集的List
// Pageable接口,常用于构造翻页查询,通常也有以下方法
int getPageNumber() //获取总页数
int getPageSize() //获取一页的行数
Pageable next() //返回Pageable类型的下一页
boolean hasPrevious() //是否有上一页
//Sort是一个用于排序的类型,默认升序排序,通常有以下用法
Sort sort = Sort.by(Direction.DESC,"id");
6、JPA自定义方法
6.1、方法命名查询
自定义的简单查询就是根据方法名来自动生成SQL,主要的语法是findByxxx,readByxxx,queryByxxx,getByxxx后面跟属性名称(实体对对象里面的属性,开头字母大写):同时支持And和or等连接词当做条件的拼接
查询的数据的方式比较多,可以根据具体语义选择不同的查询。下面演示根据订单id查询数据的所有方法。
//(1) 普通查询 => findBy +属性名
User findById(Long id);
User readById(Long id);
User queryById(Long id);
User getById(Long id);
// findBy + 属性名称(根据属性名称进行完成匹配的查询)
//(2)条件查询 => findBy +属性名称+“查询方式(Like| isnull)
User findByUserNameLike(String custName)
// 表示根据CustName字段进行模糊查询
//(3)多条件查询 => findBy +属性名+“查询方式”+ “多条件的连接符(and|or) ”+属性名+“查询方式”
findByIdLikeOrProductId("123456%","956434");
// 查询User对象Id字段进行模糊匹配,或者查询ProductId字段精准匹配
//(4)集合查询
List<User> findByIdIn(List<Long> id);
// 表示使用In关键字进行查询,如果后面不加集合查询关键字则会报Operator SIMPLE_PROPERTY on users requires a scalar argument异常
6.2、自定义SQL查询
@Modifying作用:
(1)可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作。 注意: JPQL 不支持使用 INSERT;
(2)在 @Query 注解中编写 JPQL 语句, 但必须使用 @Modifying 进行修饰. 以通知 SpringData, 这是一个 UPDATE 或 DELETE 操作
(3)UPDATE 或 DELETE 操作需要使用事务,此时需要定义 Service 层,在 Service 层的方法上添加事务操作;
(4)默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务。 他们不能完成修改操作。
(5)jpa注解操作必须设置别名,否则jpa会报错(设置别名,意思是把实体类中设置的表名拿出来进行数据库操作)
// (1)根据对象操作
@Transactional
@Modifying
@Query("update OrderDetail as u set u.productName = ?1 where u.orderId = ?2")
public void updateByOrderId(String userName,String OrderId);
//注意:?1表示,引用方法参数的第一个参数,?2表示,引用方法参数的第二个参数
//(2)根据对象操作
@Transactional
@Modifying
@Query("delete from OrderDetail where orderName like %:OrderId%")
void deleteByOrderId(String OrderName);
//(3) nativeQuery = true根据原SQL操作
@Transactional(rollbackOn = Exception.class)
@Modifying
@Query(value = "delete from aoa_role_power_list where role_id=:rId",nativeQuery = true)
Integer deleteByRoleId(Long rId);
6.3、动态组合查询
前提
DAO必须继承JpaSpecificationExecutor<实体类名>
JpaSpecificationExecutor类常用方法
T findById (Specification<T> spec); //查询单个对象
List<T> findAll (Specification<T> spec) ; // 查询列表
//查询全部,分页
//pageable:分页参数
//返回值:分页pageBean (page: 是springdatajpa提供的 )
Page<T> findAll (Speci fication<T> spec, Pageable pageable) ;
//查询列表
//Sort:排序参数
List<T> findAll(Speci fication<T> spec, Sort sort) ;
long count(Specification<T> spec) ;//统计查询
Specification查询
自定义我们自己的specification实现类
// 实现
//root:查询的根对象( 查询的任何属性都可以从根对象中获取)
//CriteriaQuery:顶层查询对象,自定义查询方式(了解: 一般不用)
//CriteriaBuilder:查询的构造器,封装了很多的查询条件
Predicate toPredicate (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
//封装查询条件
单查询条件
模糊查询
List<Student> all = stuDAO.findAll(new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// (1) 获取需要查询的属性
Path<Object> stuUser = root.get("stuUser");
Predicate like = cb.like(stuUser.as(String.class), "王%");
// 将结果返回
return like;
}
});
System.out.println(all);
排序查询
Sort sort = Sort.by(Sort.Direction.ASC,"stuBh");
List<Student> all = stuDAO.findAll(new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// (1) 获取需要查询的属性
Path<Object> stuUser = root.get("stuUser");
Predicate like = cb.like(stuUser.as(String.class), "王%");
// 将结果返回
return like;
}
},sort);
System.out.println(all);
多条件查询
自定义查询条件
- 实现Specification接口(提供泛型:查询的对象类型)
- 实现toPredicate方法(构造查询条件)
- 需要借助方法参数中的两个参数(
root:获取需要查询的对象属性
CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配)
)
List<Student> all = stuDAO.findAll(new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// (1)查询的属性
Path<Object> stuAge = root.get("stuAge");
Path<Object> stuUser = root.get("stuUser");
// (2)构造查询方式
// 1、构造stuAge的精准匹配
Predicate p1 = cb.equal(stuAge, "30"); # 参数一:path(属性名)、参数二: 属性值
// 2、构造stuUser的精准匹配
Predicate p2 = cb.equal(stuUser, "张三");
// (3)、将多个查询条件组合到一起(and表示满足条件一并且要满足条件二)
Predicate and = cb.and(p1, p2);
// (4)将结果返回
return and;
}
});
System.out.println(all);
分页查询
Page<T> findAll (Specification对象, Pageable pageable) ;
/**
*分页查询
*Specification:查询条件
*Pageable对象:分页参数
*分页参数:查询的页码,每页查询的条数
*findAll(Specification, Pageable):带有条件的分页
*findAll(Pageable):没有条件的分页
*返回: Page (springDataJpa为 我们封装好的pageBean对象,数据列表,共条数)
*/
实现
Pageable pageable = PageRequest.of(page,size);
// page:分页,页码,size:每页数据量
Sort sort = Sort.by(Sort.Direction.ASC,"stuBh");
// sort:分页对象,根据stuBh属性进行分页
Page<Student> all = stuDAO.findAll(new Specification<Student>() {
// 实现Specification接口,实现动态查询,并分页
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// (1) 获取需要查询的字段
Path<Object> stuUser = root.get("stuUser");
// (2)拼接条件
Predicate like = cb.like(stuUser.as(String.class), "王%");
// 将结果返回
return like;
}
}, pageable);
System.out.println(all.get());