JPA(Java Persistence API)是Sun官方提出的Java持久化规范,它为Java开发人员提供一种对象关联映射工具来管理Java应用中的关系数据,它的出现主要是为了简化现有持久化开发工作和整合ORM技术。
Spring Data JPA是Spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,可以让开发者用极简的代理即可实现对数据的访问和操作,提供包括增、删、改、查等在内的常用功能。Spring Data JPA其实就是spring基于Hibernate之上构建的JPA使用解决方案。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
spring:
jpa:
properties:
hibernate:
hbm2ddl:
auto: create
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
format_sql: true
show-sql: true
hibernate.hbm2ddl.auto参数的作用主要用于:自动创建、更新、验证数据表结构,一共有四个值;
dialect主要是指定生成表名的存储引擎为InnoDB
show-sql是否在日志中打印出自动生成的SQL,方便调用时查看
@Entity
@Data
public class User {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false,unique = true)
private String userName;
@Column(nullable = false,unique = true)
private String passWord;
@Column(nullable = false,unique = true)
private String email;
@Column(nullable = true,unique = true)
private String nickName;
@Column(nullable = false)
private String regTime;
}
@Entity(name="EntityName")
必须,用来标注一个数据库对应的实体,数据库中创建表名默认和类名一直,其中,name为可选,对应数据库中一个表,使用此注解标记Pojo是一个JPA实体;
@Table(name="",catalog="",schema="")
可选,用来标注一个数据库对应的实体,数据库中创建的表名默认和类名一致,通常和@Entity配置实用,只能标注在实体的class定义处,表示实体对应的数据表信息;
@Id
必须,定义了映射到数据表的主键的属性,一个实体只能有一个属性被映射为主键;
@GeneratedValue(strategy=GenerationType,genetator="")
可选,strategy表示主键生成策略,由AUTO、INDENTUTY、SEQUENCE和TABLE四种,分别表示让ORM框架自动选择,generator表示主键生成器的名称|
@Column(name="user_code",nullable=false,length=32)
可选,描述了数据库表中该字段的详细定义,这对于根据JPA注解生成数据库表结构的工具,name表示数据库表中该字段的名称,默认情形睡醒名称一致,nullable表示该字段是否允许为null,默认为true;unique表示该字段是否是唯一标识,默认为false;length表示该字段大小,仅对string类型字段有效;
@Transient
可选,表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性;
@Enumerated
可选,使用枚举类的时候,希望数据库中存储的是枚举对应的String类型,而不是枚举的索引值,需要在属性上添加Enumerated(EnumType.STRING)
注解;
创建Repository只需要继承JPARepository即可,会帮我们自动生成很多内置方法,另外还有一个非常使用的功能,可以根据方法名自动产生SQL。
public interface UserRepository extends JpaRepository<User,Long> {
User findByUserName(String userName);
User findByUserNameOrEmail(String userName,String email);
}
只需在对应的Repository中创建好方法,使用的时候直接将接口注入到类中调用即可。
JPARepository
继承PagingAndSortingRepository
和QueryByExampleExecutor
,PagingAndSortingRepository
主要负责排序和分页内容,QueryByExampleExecutor
提供了很多示例的查询方法:
public interface QueryByExampleExecutor<T> {
<S extends T> Optional<S> findOne(Example<S> var1);
<S extends T> Iterable<S> findAll(Example<S> var1);
<S extends T> Iterable<S> findAll(Example<S> var1, Sort var2);
<S extends T> Page<S> findAll(Example<S> var1, Pageable var2);
<S extends T> long count(Example<S> var1);
<S extends T> boolean exists(Example<S> var1);
}
PagingAndSortingRepository
继承自CrudRepository
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();
}
@RunWith(SpringRunner.class)
@SpringBootTest
class UserRepositoryTest {
@Resource
private UserRepository userRepository;
@Test
public void test(){
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
String formattedData = dateFormat.format(date);
userRepository.save(new User(1L,"aa","[email protected]","aa","aa123456",formattedData));
userRepository.save(new User(2L,"bb","[email protected]","bb","bb123456",formattedData));
userRepository.save(new User(3L,"cc","[email protected]","cc","cc123456",formattedData));
userRepository.save(new User(4L,"dd","[email protected]","dd","dd123456",formattedData));
Assert.assertEquals(9,userRepository.findAll().size());
Assert.assertEquals("bb",userRepository.findByUserNameOrEmail("bb","[email protected]").getNickName());
userRepository.delete(userRepository.findByUserName("aa1"));
}
}
可以将Spring Data JPA查询分为两种,一种是Spring Data JPA默认实现的,另一种是需要根据查询的情况来自行构建。
因为继承了JpaRepository
而拥有了父类的的内容
Spring Data JPA可以根据接口方法名来实现数据库操作,主要语法如下:
根据用户名查询用户:
User findByUserName(String userName);
也可以加一些关键字如And、or:
User findByUserNameOrEmail(String userName,String email);
修改、删除、统计也是类似方法:
Long deleteById(Long id);
Long countByUserName(String userName);
基本上SQL体系中的关键词都可以使用,如LIKE、IgnoreCase、OrderBy:
List<User> findByEmailLike(String email);
User findByUserNameIgnoreCase(String userName);
List<User> findByUserNameOrderByEmailDesc(String email);
Keyword | Sample | JPQL sinppet |
---|---|---|
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 ?1 (parameterbound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameterbound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameterbound 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) |
在SQL的查询方法上面使用@Query
注解,在注解内写Hql来查询内容
@Query("select u from User u")
Page<User> findALL(Pageable pageable);
同时还支持原生SQL,需要再添加一个参数nativeQuery=true
@Query(value = "select * from user u where u.nick_name =?1",nativeQuery = true)
Page<User> findByNickName(String nickName,Pageable pageable);
其中,“1”代表的参数的顺序,如果有多个参数可以通过这样来指定,还可以使用@Param
来支持;
@Query(value = "select * from user u where u.nick_name =:nickName",nativeQuery = true)
Page<User> findByNickName(@Param("nickName") String nickName, Pageable pageable);
如果涉及到删除和修改需要加上@Modifying
,也可以根据需要加上@Transactional
对事物的支持、操作超时设置等;
@Transactional(timeout = 10)
@Modifying
@Query("update User set userName = ?1 where id = ?2")
int modifyById(String userName,Long id);
@Override
@Transactional
@Modifying
@Query("delete from User where id=?1")
void deleteById(Long id);
除了使用@Query
外,还可以预先定义好一些查询,并为其命名,然后在Repository中添加相同命名的方法。
@NamedNativeQueries({
@NamedNativeQuery(name = "User.findByPassWord"
,query = "select u from User u where u.passWord = ?1"),
@NamedNativeQuery(name = "User.findByNickName111"
,query = "select u from User u where u.nickName = ?1"),
})
public class User {
...
}
List<User> findByPassWord(String passWord);
List<User> findByNickName(String nickName);
我们可以通过三种方法来定义Query:
@Query
注解实现自定义Query;@NamedQuery
注解来定义Query;可以通过配置@EnableJpaRepositories
的queryLookupStrategy
属性来配置Query的查找策略:
CREATE
:尝试从查询方法名构造特定于存储的查询,一般的方法是从方法名中删除一组已知的前缀并解析方法的其余部分;
USE_DECLARED_QUERY
:尝试查找已经声明的查询,如果找不到则抛出异常,查询可以通过某个地方的注释定义,也可以通过其他方式声明;
CREATE_IFNOTFOUND(默认)
:首先查找一个已经声明的查询,如果没有找到,它将创建一个自定义方法基于名称的查询,允许通过方法名进行快速查询定义,还可以根据需要引入声明的查询来定制这些查询调优。
Spring Data JPA已经帮我们内置了分页功能,在查询的方法中,需要传入参数Pageable,当查询中有多个参数的时候Pageable建议作为最后一个参数传入。
@Query("select u from User u")
Page<User> findALL(Pageable pageable);
Page<User> findByNickName(String nickName, Pageable pageable);
Pageable是Spring封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则,Page是Spring封装的分页对象,封装了总页数、分页数据等,返回对象除使用Page外,还可以使用Slice作为返回值;
Slice<User> findByNickNameAndEmail(String nickName, String email,Pageable pageable
);
Page和Slice的区别如下:
int page=1,size=2;
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(page, size, sort);
System.out.println(userRepository.findALL(pageable).getTotalElements());
userRepository.findByNickName("aa",pageable);
有时候我们只需要查询前N个元素,或者只取前一个实体;
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
可以通过AND获取OR等连接词来不断拼接属性来构建多条件查询,但是如果参数大于6个时,方法名会变得非常长,并且还不能解决动态条件查询的场景。
JpaSpecificationExecutor是JPA2.0提供的CriteriaAPI的使用封装,可以动态生成Query来满足我们业务中的各种复杂场景,Spring Data JPA为我们提供了JPASpecificationExecutor接口,只要简单实现toPredicate方法就可以实现复杂的查询。
public interface JpaSpecificationExecutor<T> {
//根据 Specification 条件查询单个对象,注意的是,如果条件能查出来多个会报错
T findOne(@Nullable Specification<T> spec);
//根据 Specification 条件查询 List 结果
List<T> findAll(@Nullable Specification<T> spec);
//根据 Specification 条件,分⻚查询
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
//根据 Specification 条件,带排序的查询结果
List<T> findAll(@Nullable Specification<T> spec, Sort sort);
//根据 Specification 条件,查询数量
long count(@Nullable Specification<T> spec);
}
Root root
代表了可以查询和操作的实体对象的根,通过get(“属性名”)
来获取对应的值;CriteriaQuery> query
:代表一个specific的顶层查询对象,它包含着查询的各个部分;CriteriaBuilder cb
来构建CriticaQuery的构建器对象,相当于条件或条件组合,并以Predicate形式返回。@Entity
@Data
public class UserDetail {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false,unique = true)
private Long userId;
private Integer age;
private String status;
private String realName;
private String hobby;
private String introduction;
private String lastLoginIp;
}
public interface UserDetailRepository extends JpaSpecificationExecutor<UserDetail>
,JpaRepository<UserDetail,Long> {
}
public interface UserDetailService {
public Page<UserDetail> findByCondition(DetailParam detailParam, Pageable pageable);
}
@Service
public class UserDetailServiceImpl implements UserDetailService{
@Resource
private UserDetailRepository userDetailRepository;
@Override
public Page<UserDetail> findByCondition(DetailParam detailParam, Pageable pageable) {
return userDetailRepository.findAll((root,query,cb)->{
ArrayList<Predicate> predicates = new ArrayList<>();
// equal使用
if (!StringUtils.isEmpty(detailParam.getIntroduction())){
predicates.add(cb.equal(root.get("introduction"),detailParam.getIntroduction()));
}
//like使用
if (!StringUtils.isEmpty(detailParam.getRealName())){
predicates.add(cb.like(root.get("realName"),"%"+detailParam.getRealName()+"%"));
}
//between
if (detailParam.getMinAge()!=null && detailParam.getMaxAge()!=null){
predicates.add(cb.between(root.get("age"),detailParam.getMinAge(),detailParam.getMaxAge()));
}
//greaterThan 大于等于
if (detailParam.getMaxAge()!=null){
predicates.add(cb.greaterThan(root.get("age"),detailParam.getMinAge()));
}
return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
},pageable);
}
}
测试
@Test
void findByCondition() {
int page=0,size=10;
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(page, size, sort);
DetailParam param = new DetailParam();
param.setIntroduction("Development5");
param.setMaxAge(25);
param.setMinAge(15);
Page<UserDetail> page1 = userDetailService.findByCondition(param, pageable);
for (UserDetail userDetail : page1) {
System.out.println(userDetail);
}
}
多表查询在Spring Data JPA中有两种实现方式,第一种是利用Hibernate的级联查询来实现,第二种是创建一个结果集的接口来接收连表查询后的结果;
接收结果集
public interface UserInfo {
String getUserName();
String getEmail();
String getAddress();
String getHobby();
}
@Query("select u.userName as userName, u.email as email, d.introduction as introduction , " +
"d.hobby as hobby from User u , UserDetail d " +
"where u.id=d.userId and d.hobby = ?1 ")
List<UserInfo> findUserInfo(String hobby);
@Test
public void testUserInfo(){
List<UserInfo> userInfos = userDetailRepository.findUserInfo("eat2");
for (UserInfo userInfo : userInfos) {
System.out.println(String.format("%s %s %s %s",userInfo.getAddress(),userInfo.getEmail()
,userInfo.getHobby(),userInfo.getUserName()));
}
}
Hibernate:
select
user0_.user_name as col_0_0_,
user0_.email as col_1_0_,
userdetail1_.introduction as col_2_0_,
userdetail1_.hobby as col_3_0_
from
user user0_ cross
join
user_detail userdetail1_
where
user0_.id=userdetail1_.user_id
and userdetail1_.hobby=?
配置两个数据源
datasource:
primary:
url: jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 12345678
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
url: jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 12345678
driver-class-name: com.mysql.cj.jdbc.Driver
创建DataSourceConfig添加@Configuration注解,在项目启动时运行初始化数据库资源。
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource firstDataSource(){
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource(){
return DataSourceBuilder.create().build();
}
}
@Primary
作为默认数据源使用;
加载JPA的相关配置,JPAProperties时JPA的一些属性配置信息,构建LocalEntityManagerFactoryBean需要参数信息注入到方法中。
@Autowired
private JpaProperties jpaProperties;
@Autowired
private HibernateProperties hibernateProperties;
@Bean(name = "vendorProperties")
public Map<String,Object> getVendorProperties(){
return hibernateProperties.determineHibernateProperties(
jpaProperties.getProperties()
,new HibernateSettings());
}
第一个数据源的加载配置过程
LocalEntityManagerFactoryBean
负责创建一个适合于仅使用JPA进行数据访问的环境的EntityManager,构建的时候需要指明提示实体类的包路径、数据源和JPA配置信息;
@Bean(name = "entityManagerFactoryPrimary")
@Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(
EntityManagerFactoryBuilder builder){
return builder
.dataSource(primaryDataSource)
.properties(vendorproperties)
.packages("com.example.demo")
.persistenceUnit("primaryPersistenceUnit")
.build();
}
@Bean(name = "entityManagerPrimary")
@Primary
public EntityManager entityManager(EntityManagerFactoryBuilder builder){
return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
}
EntityMabager
是JPA中用于增、删、改、查的接口,相当于一座桥梁,连接内存中的Java对象和数据库数据存储,使用EntityManager
中的相关接口对数据库实体进行操作的时候,EntityManager
会跟踪实体对象的状态,并决定在特定时刻对实体的操作映射到数据库操作上面。
同时给数据源添加上JPA事务;
@Bean(name = "transactionManagerPrimary")
@Primary
PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder){
return new JpaTransactionManager(entityManagerFactoryPrimary((builder)).getObject());
}
将我们在类中配置好的EntityManager和事务信息注入到对应数据源的repository目录下,这样目录下的repository就会拥有对应数据源和事务信息;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "entityMabagerFactoryPrimary",
transactionManagerRef = "transactionManagerPrimary",
basePackages = {
"com.example.demo.repository" }
)
public class PrimaryConfig {
}
第二个数据源的加载配置过程
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "entityMabagerFactorySecondary",
transactionManagerRef = "transactionManagerSecondary",
basePackages = {
"com.example.demo.repository" }
)
public class SecondaryConfig {
@Autowired
@Qualifier("secondaryDataSource")
private DataSource secondaryDataSource;
@Autowired
@Qualifier("vendorProperties")
private Map<String,Object> vendorproperties;
@Bean(name = "entityManagerFactorySecondary")
public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary(
EntityManagerFactoryBuilder builder){
return builder
.dataSource(secondaryDataSource)
.properties(vendorproperties)
.packages("com.example.demo")
.persistenceUnit("secondaryPersistenceUnit")
.build();
}
@Bean(name = "entityManagerSecondary")
public EntityManager entityManager(EntityManagerFactoryBuilder builder){
return entityManagerFactorySecondary(builder).getObject().createEntityManager();
}
@Bean(name = "transactionManagerSecondary")
PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder){
return new JpaTransactionManager(entityManagerFactorySecondary((builder)).getObject());
}
}