Jpa-Transaction
SimpleJpaRepository
在spring data jpa中,自定义的所有Repository接口,其实最终都是使用动态代理技术,生成代理类,最终调用的其实都是SimpleJpaRepository对象。
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
上面是SimpleJpaRepository的定义,我们可以看到,这个类添加了 @Transactional(readOnly = true)注解。下面,我们来看下这个注解的作用。
1.@Transactional(readOnly = true)是如何工作的,为什么使用它可以提高性能?
首先,让我们看一下事务接口。
/**
* A boolean flag that can be set to {@code true} if the transaction is
* effectively read-only, allowing for corresponding optimizations at runtime.
* <p>Defaults to {@code false}.
* <p>This just serves as a hint for the actual transaction subsystem;
* it will <i>not necessarily</i> cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction
* but rather silently ignore the hint.
* @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
* @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
*/
boolean readOnly() default false;
我们可以看到 readOnly = true 选项允许优化。事务管理器将使用只读选项作为提示。让我们看看用于事务管理器的JpaTransactionManager。
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
JpaTransactionObject txObject = (JpaTransactionObject) transaction;
// .
// Delegate to JpaDialect for actual transaction begin.
int timeoutToUse = determineTimeout(definition);
Object transactionData = getJpaDialect().beginTransaction(em,
new JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder()));
//...
}
在JpaTransactionManager中,doBegin方法委托JpaDialect来开始实际的事务,并在JpaDialect中调用beginTransaction。让我们来看看HibernateJpaDialect类。
@Override
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
throws PersistenceException, SQLException, TransactionException {
// ...
// Adapt flush mode and store previous isolation level, if any.
FlushMode previousFlushMode = prepareFlushMode(session, definition.isReadOnly());
if (definition instanceof ResourceTransactionDefinition &&
((ResourceTransactionDefinition) definition).isLocalResource()) {
// As of 5.1, we explicitly optimize for a transaction-local EntityManager,
// aligned with native HibernateTransactionManager behavior.
previousFlushMode = null;
if (definition.isReadOnly()) {
session.setDefaultReadOnly(true);
}
}
// ...
}
protected FlushMode prepareFlushMode(Session session, boolean readOnly) throws PersistenceException {
FlushMode flushMode = session.getHibernateFlushMode();
if (readOnly) {
// We should suppress flushing for a read-only transaction.
if (!flushMode.equals(FlushMode.MANUAL)) {
session.setHibernateFlushMode(Flusode.MANUAL);
return flushMode;
}
}
else {
// We need AUTO or COMMIT for a non-read-only transaction.
if (flushMode.lessThan(FlushMode.COMMIT)) {
session.setHibernateFlushMode(FlushMode.AUTO);
return flushMode;
}
}
// No FlushMode change needed...
return null;
}
在JpaDialect中,我们可以看到JpaDialect使用只读选项准备刷新模式。当 readOnly = true 时, JpaDialect 禁止刷新。此外,您还可以看到,在准备刷新模式后,session.setDefaultReadOnly(true)将session的readOnly属性设置为true。
/**
* Change the default for entities and proxies loaded into this session
* from modifiable to read-only mode, or from modifiable to read-only mode.
*
* Read-only entities are not dirty-checked and snapshots of persistent
* state are not maintained. Read-only entities can be modified, but
* changes are not persisted.
*
* When a proxy is initialized, the loaded entity will have the same
* read-only/modifiable setting as the uninitialized
* proxy has, regardless of the session's current setting.
*
* To change the read-only/modifiable setting for a particular entity
* or proxy that is already in this session:
* @see Session#setReadOnly(Object,boolean)
*
* To override this session's read-only/modifiable setting for entities
* and proxies loaded by a Query:
* @see Query#setReadOnly(boolean)
*
* @param readOnly true, the default for loaded entities/proxies is read-only;
* false, the default for loaded entities/proxies is modifiable
*/
void setDefaultReadOnly(boolean readOnly);
在Session接口中,通过将readOnly属性设置为true,将不会对只读实体进行脏检查,也不会维护持久状态的快照。此外,只读实体的更改也不会持久化。
总而言之,这些是在 Hibernate 中使用@Transactional(readOnly = true)所得到的结果
性能改进:只读实体不进行脏检查
节省内存:不维护持久状态的快照
数据一致性:只读实体的更改不会持久化
当我们使用主从或读写副本集(或集群)时,@Transactional(readOnly = true)使我们能够连接到只读数据库
2、当我们使用 JPA 时,是否应该总是将@Transactional(readOnly = true)添加到服务层的只读方法?有什么取舍吗?
我看到,当使用@Transactional(readOnly = true)时,我们可以有很多优势。但是,将@Transactional(readOnly = true)添加到服务层的只读方法是否合适?以下是我担心的事情
无限制地使用事务可能会导致数据库死锁、性能和吞吐量下降。
由于一个事务占用一个DB连接,所以@Transactional(readOnly = true)添加到Service层的方法可能会导致DB连接饥饿。
第一个问题很难重现,所以我做了一些测试来检查第二个问题。
@Transactional(readOnly = true)
public List<UserDto> transactionalReadOnlyOnService(){
List<UserDto> userDtos = userRepository.findAll().stream()
.map(userMapper::toDto)
.toList();
timeSleepAndPrintConnection();
return userDtos;
}
public List<UserDto> transactionalReadOnlyOnRepository(){
List<UserDto> userDtos = userRepository.findAll().stream()
.map(userMapper::toDto)
.toList();
timeSleepAndPrintConnection();
return userDtos;
}
我在服务层测试了两个方法,一个是@Transactional(readOnly = true),另一个是存储库层中的@Transactional (readOnly = true)(在 SimpleJpaRepository 中,它是 Jpa Respitory 的默认实现,在类的顶部有@Transformational(ready Only),因此 findAll()方法在默认情况下有@transactional(read only = True))。
我从DB中获取userInfo并保持线程5秒钟,然后检查该方法何时释放连接。
结果如下:
对于服务层方法中的@Transactional(readOnly = true),
activeConnections:0, IdleConnections:10, TotalConnections:10
start transactionalReadOnlyOnService!!
Hibernate:
select
u1_0.id,
u1_0.email,
u1_0.name,
u1_0.profile_file_name
from
users u1_0
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
end transactionalReadOnlyOnService!!
activeConnections:0, IdleConnections:10, TotalConnections:10
对于存储库层方法中的@Transactional(readOnly = true),
activeConnections:0, IdleConnections:10, TotalConnections:10
start transactionalReadOnlyOnRepository!!
Hibernate:
select
u1_0.id,
u1_0.email,
u1_0.name,
u1_0.profile_file_name
from
users u1_0
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
end transactionalReadOnlyOnRepository!!
activeConnections:0, IdleConnections:10, TotalConnections:10
正如您所看到的,@Transactional(readOnly = true)一旦查询结果到达,存储库层就会释放连接。
然而,@Transactional(readOnly = true)在服务层的方法中直到服务层的方法结束才释放连接。
因此,当服务层的方法有需要大量时间的逻辑时要小心,因为它可以长时间持有数据库连接,这可能会导致数据库连接匮乏。
3.增删改查方法的事务情况
由于在SimpleJpaRepository类上,添加了 @Transactional(readOnly = true)注解,所以,默认SimpleJpaRepository类的所有方法,都启用了事务,包括查询方法也是启用了事务。
对于保存 和 查询方法,他是在方法上,单独添加了 @Transactional注解,表示自动启用事务,且不仅仅是一个只读事务,覆盖了类上的 @Transactional(readOnly = true)注解。
代码如下:
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, ENTITY_MUST_NOT_BE_NULL);
if (entityInformation.isNew(entity)) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}
@Transactional
public void deleteById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
findById(id).ifPresent(this::delete);
}
通过上面的讲解,我们知道了,在JPA中,即使是查询,也是开启了事务的。这样做,其实容易出现一个问题,就是事务之间容易出现死锁或者事务锁定,导致性能下降。
那么,如果我们想让查询方法,不开启事务,应该怎么做呢?
自定义查询无事务的Repository
为了执行查询方法时,不开启事务。我们需要自定义一个Repository,如下:
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public class QueryNoTransactionRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> {
public QueryNoTransactionRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
}
public QueryNoTransactionRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
}
然后,我们需要配置 @EnableJpaRepositories注解上,配置repositoryBaseClass
@EnableJpaRepositories(repositoryBaseClass = QueryNoTransactionRepositoryImpl.class)
这样,在使用动态代理技术,生成代理类,最终调用的是我们自定义的QueryNoTransactionRepositoryImpl对象,不再是原来的SimpleJpaRepository对象。
另外,我们需要在 QueryNoTransactionRepositoryImpl类上,添加 @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)。
这个注解的含义是,如果当前没有事务,那么查询方法不需要开启事务;如果当前有事务,那么调用查询方法时,查询方法就加入这个事务。
另外,由于这个QueryNoTransactionRepositoryImpl是继承了SimpleJpaRepository,而SimpleJpaRepository的查询方法,都是自动开启只读事务的,所以,需要需要在子类QueryNoTransactionRepositoryImpl中,重写所有的查询方法。如果不重写查询方法,那么java默认规则是,会把父类的只读事务注解也继承过来了。
重写代码如下:
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public class QueryNoTransactionRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> {
public QueryNoTransactionRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
}
public QueryNoTransactionRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
}
@Override
public Optional<T> findById(ID id) {
return super.findById(id);
}
@Override
public T getReferenceById(ID id) {
return super.getReferenceById(id);
}
@Override
public boolean existsById(ID id) {
return super.existsById(id);
}
@Override
public List<T> findAll() {
return super.findAll();
}
@Override
public List<T> findAllById(Iterable<ID> ids) {
return super.findAllById(ids);
}
@Override
public List<T> findAll(Sort sort) {
return super.findAll(sort);
}
@Override
public Page<T> findAll(Pageable pageable) {
return super.findAll(pageable);
}
@Override
public Optional<T> findOne(Specification<T> spec) {
return super.findOne(spec);
}
@Override
public List<T> findAll(Specification<T> spec) {
return super.findAll(spec);
}
@Override
public Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable) {
return super.findAll(spec, pageable);
}
@Override
public List<T> findAll(@Nullable Specification<T> spec, Sort sort) {
return super.findAll(spec,sort);
}
@Override
public boolean exists(Specification<T> spec) {
return super.exists(spec);
}
@Override
public <S extends T, R> R findBy(Specification<T> spec, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
return super.findBy(spec, queryFunction);
}
@Override
public <S extends T> Optional<S> findOne(Example<S> example) {
return super.findOne(example);
}
@Override
public <S extends T> long count(Example<S> example) {
return super.count(example);
}
@Override
public <S extends T> boolean exists(Example<S> example) {
return super.exists(example);
}
@Override
public <S extends T> List<S> findAll(Example<S> example) {
return super.findAll(example);
}
@Override
public <S extends T> List<S> findAll(Example<S> example, Sort sort) {
return super.findAll(example,sort);
}
@Override
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
return super.findAll(example,pageable);
}
@Override
public <S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
return super.findBy(example, queryFunction);
}
@Override
public long count() {
return super.count();
}
@Override
public long count(@Nullable Specification<T> spec) {
return super.count(spec);
}
}
可以看到,其实重写方法,就是直接调用super即可。唯一的作用是,通过重写机制,保证了不会将父类方法中的只读事务继承过来,从而实现了,查询时,不开启事务。
重写后,测试查询时是否开启了事务
随便调用一个查询方法,通过debug追踪,此时会发现,只是调用到了JpaTransactionManager#doGetTransaction方法,但是没有进入到JpaTransactionManager#doBegin方法,也就是说,实际上并没有开启事务,目标达成。
文章作者:Administrator
文章链接:http://localhost:8090//archives/jpa-transaction
版权声明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0 许可协议,转载请注明出处!
评论