未完待续
spring jpa
1. 参考链接
https://www.jianshu.com/p/c23c82a8fcfc
https://docs.spring.io/spring-data/jpa/docs/2.4.11/reference/html/#preface
https://www.baeldung.com/spring-rest-api-query-search-language-tutorial
2. 核心类
Repository, CrudRepository, JpaRepository
JpaSpecificationExecutor, PagingAndSortingRepository
3. 使用方式
3.1. 声明接口
interface PersonRepository extends Repository<Person, Long> { … }
3.2. 定义方法
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}
3.3. 让spring开启代理
- javaconfig方式
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableJpaRepositories
class Config { … }
- xml方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jpa:repositories base-package="com.acme.repositories"/>
</beans>
注意:不同的存储类型,使用相应的store(如jpa,mongodb), @Enable${store}Repositories
3.4. 使用
class SomeClient {
private final PersonRepository repository;
SomeClient(PersonRepository repository) {
this.repository = repository;
}
void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}
4. 查询方法定义
代理通过两种方式生成查询语句
- 根据方法名自动生成
- 指定查询语句
4.1. 策略
Enable${store}Repositories的queryLookupStrategy属性指定
- create: 移除方法名的通用前缀,解析方法名获取
- use_declared_query: 通过注解指定
- Create_if_not_found:上面两种方式的混合
5. 自定义接口实现
参考本文下方的Dynamic Query
Repository默认spring是通过代理组装的,寻找的是Impl结尾的接口实现,可通过Enable${store}Repositories的repositoryImplementationPostfix设置
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
// 会借由CustomizedUserRepositoryImpl实现someCustomMethod方法,且优先级高于CrudRepository
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
6. 事件驱动
https://www.baeldung.com/spring-data-ddd
当有save进行时,通过ApplicationEventPublisher发布@DomainEvent标记的事件,也可以继承AbstractAggregateRoot
@Entity
class AnAggregateRoot {
// 不能有参数
@DomainEvents
Collection<Object> domainEvents() {
// … return events you want to get published here
}
@AfterDomainEventPublication
void callbackMethod() {
// … potentially clean up domain events list
}
}
7. REST Query Language with Spring Data JPA Specifications
https://www.baeldung.com/rest-api-search-language-spring-data-specifications
和mybatisplus里的lambda类似, 需要继承JpaSpecificationExecutor
public interface UserRepository
extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {}
public class UserSpecification implements Specification<User> {
private SearchCriteria criteria;
@Override
public Predicate toPredicate
(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
if (criteria.getOperation().equalsIgnoreCase(">")) {
return builder.greaterThanOrEqualTo(
root.<String> get(criteria.getKey()), criteria.getValue().toString());
}
else if (criteria.getOperation().equalsIgnoreCase("<")) {
return builder.lessThanOrEqualTo(
root.<String> get(criteria.getKey()), criteria.getValue().toString());
}
else if (criteria.getOperation().equalsIgnoreCase(":")) {
if (root.get(criteria.getKey()).getJavaType() == String.class) {
return builder.like(
root.<String>get(criteria.getKey()), "%" + criteria.getValue() + "%");
} else {
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
}
}
return null;
}
}
实现自定义搜索,类似es
@Controller
public class UserController {
@Autowired
private UserRepository repo;
@RequestMapping(method = RequestMethod.GET, value = "/users")
@ResponseBody
public List<User> search(@RequestParam(value = "search") String search) {
UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
// Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),", Pattern.UNICODE_CHARACTER_CLASS);
Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
}
Specification<User> spec = builder.build();
return repo.findAll(spec);
}
}
8. 主键策略
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
9. 审计
https://docs.spring.io/spring-data/jpa/docs/2.4.11/reference/html/#auditing
https://www.baeldung.com/database-auditing-jpa
@EnableJpaAuditing
@EntityListeners
@CreatedDate @CreatedBy @LastModifiedDate @LastModifiedBy
9.1. 使用@EnableJpaAuditing注解开启审计
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories
@EnableJpaAuditing
public class PersistenceConfig { ... }
9.2. 在实体上配置监听
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar { ... }
9.3. 配置审计字段
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {
//...
@Column(name = "created_date", nullable = false, updatable = false)
@CreatedDate
private long createdDate;
@Column(name = "modified_date")
@LastModifiedDate
private long modifiedDate;
@Column(name = "created_by")
@CreatedBy
private String createdBy;
@Column(name = "modified_by")
@LastModifiedBy
private String modifiedBy;
//...
}
10. 多EntityManager
- 设置持久化单元名称
// 设置repository的属性
@EnableJpaRepositories(
basePackages = {"com.fan.db.repository.common"},
entityManagerFactoryRef = "commonEntityManagerFactory",
transactionManagerRef = "commonTransactionManager"
)
---
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.acme.domain");
factory.setDataSource(dataSource());
factory.setPersistenceUnitName("fan");// 持久化单元名称
return factory;
}
- 通过JpaContext获取
class UserRepositoryImpl implements UserRepositoryCustom {
private final EntityManager em;
@Autowired
public UserRepositoryImpl(JpaContext context) {
this.em = context.getEntityManagerByManagedType(User.class);
}
…
}
11. 类型转换
@javax.persistence.Convert(converter = ApiSourceConverter.class)
12. 通用注解
@Version
@Column
@Enumerated
@Basic
@Transient
@NoRepositoryBean
标记该类不会被Spring Data 创建实例,如CrudRepository上就有
14. 实体关系
@OneToMany
@ManyToOne @JoinColumn
hibernate
org.hibernate.annotations.Type
@Type(type = "pg-uuid")
PostUpdateEventListener, PostLoadEventListener, PersistEventListener, MergeEventListener
15. @Query
https://www.baeldung.com/spring-data-jpa-query
15.1. jpql & native
@Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();
@Query(
value = "SELECT * FROM USERS u WHERE u.status = 1",
nativeQuery = true)
Collection<User> findAllActiveUsersNative();
15.2. order
- 只针对jpql时,传递额外的Sort参数,会被自动翻译为 ORDER BY语句,否则出现如下异常
org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination
- Sort定义时,只支持对象的属性名,不是数据库表的字段名
userRepository.findAll(Sort.by(Sort.Direction.ASC, "name"));
// 如下会报异常:org.springframework.data.mapping.PropertyReferenceException: No property LENGTH(name) found for type User!
userRepository.findAll(Sort.by("LENGTH(name)"));
- 包含部分函数时,需要采用JpaSort方式进行定义
userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));
15.3. pagination
- jpql
可以传递一个PageRequest参数
@Query(value = "SELECT u FROM User u ORDER BY id")
Page<User> findAllUsersWithPagination(Pageable pageable);
- native
需要增加一个countQuery去统计总数
@Query(
value = "SELECT * FROM Users ORDER BY id",
countQuery = "SELECT count(*) FROM Users",
nativeQuery = true)
Page<User> findAllUsersWithPagination(Pageable pageable);
15.4. Indexed Query Parameters
从1开始,按参数顺序,注意?开头
@Query("SELECT u FROM User u WHERE u.status = ?1")
User findUserByStatus(Integer status);
@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")
User findUserByStatusAndName(Integer status, String name);
15.5. Named Parameters
需要@Param注解,注意:开头
@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByStatusAndNameNamedParams(
@Param("status") Integer status,
@Param("name") String name);
15.6. Collection Parameter
@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List<User> findUserByNameList(@Param("names") Collection<String> names);
15.7. Update Queries With @Modifying
The return value defines how many rows the execution of the query updated,需要添加Modifying注解
@Modifying
@Query("update User u set u.status = :status where u.name = :name")
int updateUserSetStatusForName(@Param("status") Integer status,
@Param("name") String name);
// 注意这里使用?传参
@Modifying
@Query(value = "update Users u set u.status = ? where u.name = ?",
nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);
To perform an insert operation, we have to both apply @Modifying and use a native query since INSERT is not a part of the JPA interface:
@Modifying
@Query(
value =
"insert into Users (name, age, email, status) values (:name, :age, :email, :status)",
nativeQuery = true)
void insertUser(@Param("name") String name, @Param("age") Integer age,
@Param("status") Integer status, @Param("email") String email);
15.8. Dynamic Query
- Custom Repositories and the JPA Criteria API
定义接口及实现,注意实现类的命名规范,参考本文上方的自定义接口实现, 可以参考Criterial的使用
public interface UserRepositoryCustom {
List<User> findUserByEmails(Set<String> emails);
}
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<User> findUserByEmails(Set<String> emails) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
Path<String> emailPath = user.get("email");
List<Predicate> predicates = new ArrayList<>();
for (String email : emails) {
predicates.add(cb.like(emailPath, email));
}
query.select(user)
.where(cb.or(predicates.toArray(new Predicate[predicates.size()])));
return entityManager.createQuery(query)
.getResultList();
}
}
- Extending the Existing Repository
public interface UserRepository extends JpaRepository<User, Integer>, UserRepositoryCustom {
// query methods from section 2 - section 7
}
- Using the Repository
Set<String> emails = new HashSet<>();
// filling the set with any number of items
userRepository.findUserByEmails(emails);