首先了解一下 JPA,Spring Data Jpa,之间的关系
JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。
JPA包括以下3方面的技术:
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的JaveEE架构中取代CMP,完成数据持久化的重任
Hibernate是Spring Data Jpa得默认实现方式
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
8.0.11
server:
port: 8011
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/jpa_db?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=GMT%2b8&characterEncoding=utf8&connectTimeout=10000&socketTimeout=3000&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: 123456
jpa:
database: mysql
show-sql: true
hibernate:
#自动创建或修改表结构
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties:
hibernate:
jdbc:
batch_size: 30
batch_versioned_data: true
#启用插入批处理
order_inserts: true
#启用修改批处理
order_updates: true
#检测批处理开关是否打开
generate_statistics: true
#设置hibernate方言使用mysql的InnoDBD引擎,InnoDBD支持事务
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
#启用懒加载
enable_lazy_load_no_trans: true
@Entity
@Table(name = "sys_user")
public class User{}
@Index注解用来添加表的索引,可以指定单个字段和多个字段,多个字段之间用 ’ , ’ 隔开
@Entity
@Table(name = "sys_user",indexes = {
@Index(name = "username", columnList = "username"),
@Index(name = "address_age", columnList = "address,age")
}
public class User{
private String username;
private String address;
private Integer age;
}
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column 注释定义了将成员属性映射到关系表中的哪一列和该列的结构信息,属性如下:
属性名 | 描述 |
---|---|
name | 映射的列名。如:映射tbl_user表的name列,可以在name属性的上面或getName方法上面加入; |
unique | 是否唯一; |
nullable | 是否允许为空; |
length | 对于字符型列,length属性指定列的最大字符长度; |
insertable | 是否允许插入; |
updatable | 是否允许更新; |
@Column(name = "username", unique = true, nullable = false,updatable = false,length = 36)
private String username;
@Transien就是在给某个javabean上需要添加个属性,但是这个属性你又不希望给存到数据库中去,仅仅是做个临时变量,用一下。不修改已经存在数据库的数据的数据结构。
@Transient
private String ignoreColumn;
@Inheritance(strategy= InheritanceType.TABLE_PER_CLASS)//选择继承策略
@MappedSuperclass
public class BaseModel implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private Date createdDate;
}
@Entity
@Table(name = "sys_user")
public class User extends BaseModel{
//这里就不用再写通用的字段了。
}
Hibernate继承映射在 Annotation 中使用 @Inheritance 注解,并且需要使用 strategy 属性指定继承策略,继承策略有 SINGLE_TABLE、TABLE_PER_CLASS 和 JOINED 三种。
我们一般对记录的创建和修改需要手动set操作时间
user.setCreateDate(new Date());
但是通过使用Jpa的审计功能,就可以交给Jpa去实现了.
在启动类里添加注解
@EnableJpaAuditing
public class SpringDatajpaApplication {}
在需要使用的类上面添加审计监听器
在需要使用审计功能的类上面加@EntityListeners注解指定监听类为AuditingEntityListener
一般我们会这些审计的通用字段放到超类里面
@EntityListeners(AuditingEntityListener.class)
public class BaseModel {}
我这里写死了是admin,实际使用场景应该获取当前登录人的用户名
@Component
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.of("admin");
}
}
@Inheritance(strategy= InheritanceType.TABLE_PER_CLASS)//选择继承策略
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseModel implements Serializable {
@CreatedDate
@Column(name = "created_date", updatable = false)
private Date createdDate;
@CreatedBy
@Column(name = "created_by", updatable = false, length = 64)
private String createdBy;
@LastModifiedDate
@Column(name = "updated_date")
private Date updatedDate;
@LastModifiedBy
@Column(name = "updated_by", length = 64)
private String updatedBy;
}
@Entity
@Table(name = "sys_user")
@Data
public class User extends BaseMode{
}
在我们的日常开发中,有时候会用到数据库进行设计的时候,采用了复合主键来来保证唯一性,
hibernate的 @EmbeddedId 嵌入式主键应用于实体类或映射超类的持久字段或属性,以表示可嵌入类的组合主键。 可嵌入的类必须标注为 @Embeddable 下面介绍一下采用
下面以用户角色关联表介绍.
@Entity
@Table(name = "user_role")
@Data
public class UserRole {
public UserRole(){ }
public UserRole(Integer userId, Integer roleId){
this.id=new PK(userId,roleId);
}
@EmbeddedId
private PK id;
/**
* 联合主键
*/
@Embeddable
@Data
public static class PK implements Serializable {
public PK() { }
public PK(int userId, Integer roleId) {
this.userId = userId;
this.roleId = roleId;
}
/**
* 用户id
*/
private Integer userId;
/**
* 角色id
*/
private Integer roleId;
}
}
创建一个接口,然后继承JpaRepository
public interface UserRepository extends JpaRepository<User,Integer> {}
我这边列出了一些常用的方法
@Autowired
UserRepository userRepository;
userRepository.findAll();
下面的方法可以直接通过userRepository直接调用
//查询所有信息
List<T> findAll();
//保存并且刷新
<S extends T> S saveAndFlush(S var1);
//根据主键删除
void deleteById(ID var1);
//根据实体对象删除
void delete(T var1);
//根据主键获取对象信息
T getOne(ID var1);
//添加或则修改
<S extends T> S save(S var1);
Spring Data Jpa通过解析方法名创建查询,框架在进行方法名解析时,会先把方法名多余的前缀find…By, read…By, query…By, count…By以及get…By截取掉,然后对剩下部分进行解析,第一个By会被用作分隔符来指示实际查询条件的开始。 我们可以在实体属性上定义条件,并将它们与And和Or连接起来,从而创建大量查询:
关键字 | 示例 | JPQL |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ? (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ? (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
下标重1开始
@Query("select u from User u where u.username = ?1 and u.password = ?2")
User getByUsernameAndPassword(String username, String password);
@Query("select u from User u where u.username = ?1 and u.password = ?2")
User getByUsernameAndPassword(String username, String password);
@Query("select u from User u where u.username = :username and u.password = :password")
User getByUsernameAndPassword(@Param("username")String username,@Param("password") String password);
需要在@Query注解中加nativeQuery = true
注意,from后面对应的是表名称,而不是实体类的名称
@Query(value = "select u.* from sys_user u where u.username = :username and u.password = :password",nativeQuery = true)
User getByUsernameAndPasswordSQl(@Param("username")String username,@Param("password") String password);
单独使用@Query注解只是查询,如涉及到修改、删除则需要再加上@Modifying和@Transactional注解
@Transactional
@Modifying
@Query("delete from User where id=?1")
void deleteByUserId(Integer id);
Page<User> findAllByUsernameLike(String username, Pageable pageable);
构建Pageable 对象的方法
参数 | 描述 |
---|---|
page | 页码,重0开始 |
size | 每页显示的数量 |
direction | 排序类型, Sort.Direction.DESC是降序,Sort.Direction.ASC是升序 |
properties | 排序的字段,可变参数。 |
//2.0版本之前
new PageRequest(int page, int size);
new PageRequest(int page, int size, Direction direction,String... properties);
//2.0版本之后
PageRequest.of(int page, int size);
PageRequest.of(int page, int size, Direction direction, String... properties);
完整代码
Pageable pageable = PageRequest.of(0, 10, Sort.Direction.ASC, "id");
Page<User> userPage = userRepository.findAllByUsernameLike("user%", pageable);
System.out.println("总记录数"+userPage.getTotalElements()+",总页数"+userPage.getTotalPages());
for (User user : userPage.getContent()) {
System.out.println(user.getUsername());
}
对于查询条件不固定的情况下,JPQL实现不了这种情况,这个时候要继承JpaSpecificationExecutor
接口来实现动态参数查询了.
JpaSpecificationExecutor接口主要的2个方法
List<T> findAll(@Nullable Specification<T> var1);
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
首先继承JpaSpecificationExecutor接口
public interface UserRepository extends JpaRepository<User,Integer>, JpaSpecificationExecutor {}
然后组装Specification对象
String startDate = "2020-05-22";
String endDate = "2020-05-25";
String username = "dominick";
Specification<User> querySpecifi = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<>();
if (!StringUtils.isEmpty(startDate)) {
//大于或等于传入时间
predicates.add(cb.greaterThanOrEqualTo(root.get("createDate").as(String.class), startDate));
}
if (!StringUtils.isEmpty(endDate)) {
//小于或等于传入时间
predicates.add(cb.lessThanOrEqualTo(root.get("endDate").as(String.class), endDate)));
}
if (!StringUtils.isEmpty(username)) {
//模糊查询,需要自己手动拼接%%字符
predicates.add(cb.like(root.get("username").as(String.class), "%" + username + "%"));
}
// and到一起的话所有条件就是且关系,or就是或关系
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
List<User> userList = userRepository.findAll(querySpecifi);
通过EntityManagerFactory构建原生sql查询
下面只是简单的示范了怎么用,然后可以根据需求写对应的sql,能用sql解决的都不是难事。。
public interface UserCustomRepository {
List<Object[]> findBynativeQuery(User user);
}
@Component
public class UserCustomImplRepsotory implements UserCustomRepository {
private EntityManagerFactory emf;
@PersistenceUnit
public void setEntityManagerFactory(EntityManagerFactory emf) {
this.emf = emf;
}
@Override
public List<Object[]> findBynativeQuery(User user) {
EntityManager em = emf.createEntityManager();
try {
Query query = null;
StringBuilder sql = new StringBuilder("select * from sys_user where 1=1 ");
List<Object> condition = new ArrayList<>();
int index = 0;
if (!StringUtils.isEmpty(user.getUsername())) {
index++;
condition.add(user.getUsername());
sql.append(" and username=?" + index);
}
if (!StringUtils.isEmpty(user.getChannelId())) {
index++;
condition.add(user.getChannelId());
sql.append(" and channelId=?" + index);
}
//创建query对象
query = em.createNativeQuery(sql.toString());
//注入参数
if (index != 0) {
for (int i = 1; i <= index; i++) {
//query的parameter 下标起始位置重1开始的
query.setParameter(i, condition.get(i - 1));
}
}
List<Object[]> success = query.getResultList();
em.close();
return success;
} finally {
if (em != null) {
em.close();
}
}
}
}
调用代码
@Autowired
UserCustomImplRepsotory userCustomImplRepsotory;
User user=new User();
user.setUsername("dominick_li");
user.setChannelId(1);
List<Object[]> list=userCustomImplRepsotory.findBynativeQuery(user);
for(Object[] objs:list){
System.out.println(objs[0]+","+objs[1]);
}
下面级联查询我都用了懒加载
fetch = FetchType.LAZY
需要在配置文件把懒加载的开关打开
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true #开启懒加载
使用@OneToOne注解实现一对一的关系映射
@JoinColumn中的name标识当前实体的外键字段,referencedColumnName标识用户表roleId和Role表的关联字段。
我这里用 用户表和角色表介绍,省略了其它字段和注解,只描述重点
public class User {
private Integer roleId;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="roleId",referencedColumnName="id",insertable = false, updatable = false)
private Role role;
}
public class Role(
private Integer id;
private String roleName;
)
User user=userRepository.getOne(id);;
System.out.println("角色名称是:"+user.getRole().getRoleName());
一对多使用 @OneToMany 注解实现一对一关系映射
@JoinColumn name属性指的是多的一方外键,也就是用户表的channelId属性,referencedColumnName表示当前类和用户类关联的列,默认是主键
我这里使用渠道表和用户表介绍
public class User {
private Integer channelId;
}
public class Channel {
private Integer id;
private String channelName;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name="channelId")
private List<User> users;
}
Channel channel = channelRepository.getOne(id);
System.out.println(channel.getChannelName() + "有" + channel.getUsers().size() + "个用户");
多对一使用 @ManyToOne 注解进行关系映射
@JoinColumn name属性指的是多的一方外键,也就是user表的channelId属性,referencedColumnName表示渠道表和用户关联的列,默认是主键
我这里使用用户表和渠道表作介绍
public class User{
private Integer id;
private Integer channelId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="channelId",referencedColumnName="id",insertable = false, updatable = false)
private Channel channel;
}
private class Channel{
private Intger id;
private String channelName;
}
User user=userRepository.getOne();
System.out.println("渠道名称是:"+user.getChannel().getChannelName());
多对多关系用 @ManyToMany 注解进行关系映射
还需要依赖@JoinTable注解关联 关系表
public class User{
private int id;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "user_role", joinColumns = {@JoinColumn(name = "userId")},
inverseJoinColumns = {@JoinColumn(name = "roleId")})
private List<Role> roles;
}
public class Role{
private int id;
}
public class UserRole{
private Intger userId;
private Intger roleId;
}
User user=userRepository.getOne();
System.out.println("用户拥有:"+user.getRoles().size()+"个角色");
jpa默认的saveAll是在里面通关while不停的调用 save方法,假设要添加500条数据,则需要
执行500条insert语句才能完成操作,sql执行时间会长,jpa提供了批量执行sql的功能,下面
我来带大家开启批处理功能
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 50
#检测批处理开关是否打开
generate_statistics: true
属性 | 描述 |
---|---|
batch_size | 设置批处理一次处理多少条数据,如果大小设置为50,假设执行saveAll的数据300条,则需要执行6次sql才能处理完请求。 |
generate_statistics | 是否检测批处理开关是否打开 |
注意: 如果设置主键为自增类型,则批处理还是会失效。如果想使用批处理,尽量用雪花算法生成ID或者UUID
Model
@Entity
@Table(name = "orders")
@Data
public class Orders {
@Id
private String id;
private String orderName;
}
Dao层
public interface OrdersRepository extends JpaRepository<Orders,String> {
}
web层
@RestController
public class OrdersController {
@Autowired
OrdersRepository ordersRepository;
@RequestMapping("/saveAll")
public String saveAll(){
Orders orders=null;
List<Orders> ordersList=new ArrayList<>(100);
for(int i=0;i<300;i++){
orders=new Orders();
//订单表的主键应该使用雪花算法之类生成,雪花算法生成的id是有序的,这样索引查询起来会快很多,uuid是无序的,查询效率极低。。。。
orders.setId(UUID.randomUUID().toString());
orders.setOrderName("订单_"+1);
ordersList.add(orders);
}
ordersRepository.saveAll(ordersList);
return "success";
}
}
SQL Stat View JSON API 是druid连接池的一个监控工具,我会在下一章博客教大家使用
可以看到下面执行数是300
可以看到sql执行数是6,和我们预期的一致。
下面是控制打印的
github地址
要是觉得我写的对你有点帮助的话,麻烦在github上帮我点 Star
【SpringBoot框架篇】其它文章如下,后续会继续更新。