默认情况下,Spring Data JPA提供的CRUD方法都添加了事务,这里的事务使用的是Spring的事务管理机制。对于读操作来说,事务的readOnly
属性是设置的true(默认值是false),而其他操作都是设置的一个空的@Transactional
注解,所以使用的都是Spring事务的默认配置。
如果你想覆盖某个方法的事务配置,可以在自己的接口里面覆盖那个方法,然后加上Transactional
注解。
public interface UserRepository extends CrudRepository {
@Override
@Transactional(timeout = 10)
public List findAll();
}
你也可以在接口上面使用@Transactional
注解进行统一配置:
@Transactional(readOnly = true)
public interface UserRepository extends JpaRepository {
List findByLastname(String lastname);
@Modifying
@Transactional
@Query("delete from User u where u.active = false")
void deleteInactiveUsers();
}
如果同时在接口和方法上进行了配置,方法上的配置会具有更高的优先级。
对查询语句设置readOnly
为true有什么好处呢?如果你设置了readOnly
为true,hibernate的flush模式会自动设置为NEVER
,这样就可以让hibernate跳过“脏检查”阶段,可以显著提升大对象(很多层子对象组成的对象树)的查询性能。
如果涉及到多个Repository的事务,可以在Service层做处理,或者使用门面模式。这里可能涉及到“事务的传播级别”方面的知识点。
示例代码:
@Service
class UserManagementImpl implements UserManagement {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
@Autowired
public UserManagementImpl(UserRepository userRepository, RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}
@Transactional
public void addRoleToAllUsers(String roleName) {
Role role = roleRepository.findByName(roleName);
for (User user : userRepository.findAll()) {
user.addRole(role);
userRepository.save(user);
}
}
@Lock
注解并选择锁的类型即可:interface UserRepository extends Repository {
@Lock(LockModeType.READ)
List findByLastname(String lastname);
}
这个注解是在org.springframework.data.jpa.repository包下,里面只有LockModeType value这一个属性。
需要注意的是,要在事务里面使用锁
,否则会报TransactionRequiredException
异常。
同时,我们也可以在EntityManager中使用锁:
// find的时候指定锁
entityManager.find(Department.class, 1, LockModeType.PESSIMISTIC_READ);
// find后锁住一个实体
Department department = entityManager.find(Department.class, 1);
entityManager.lock(department, LockModeType.PESSIMISTIC_READ);
// 锁住query results
TypedQuery query = entityManager
.createQuery("select d from Department d", Department.class);
query.setLockMode(LockModeType.PESSIMISTIC_READ);
List departments = query.getResultList();
LockModeType
是一个枚举类,这个类是在javax.persistence
包下的。打开这个枚举即可看到所有锁的类型。public enum LockModeType
{
READ, // 与下面的OPTIMISTIC同义
WRITE, // 与下面的OPTIMISTIC_FORCE_INCREMENT同义
OPTIMISTIC,
OPTIMISTIC_FORCE_INCREMENT,
PESSIMISTIC_READ,
PESSIMISTIC_WRITE,
PESSIMISTIC_FORCE_INCREMENT,
NONE // 无锁
}
下面分别介绍这些锁。首先READ与WRITE是别名,NONE是无锁,所以不作过多解释。主要介绍这五个锁:
这两种锁都可以避免两个事务中的其中一个,在不知情的情况下覆盖另一个事务的数据。
以OPTIMISTIC
开头的代表“乐观锁”,以PESSIMISTIC
开头的是“悲观锁”。乐观锁和悲观锁有什么区别?
乐观锁是在JPA层面实现的。
顾名思义,乐观锁比较“乐观”,它假设冲突很少发生,即使发生,抛出一个错误也比想办法避免它们更容易接受和简单。核心思想就是在实体中定义一个“version”字段,可以是数值类型或时间戳类型。在读取的时候,就取到这个字段。然后在修改了实体的数值,保存的时候,就会去数据库对比这个version,如果version一致,就执行更新,否则就不执行更新。@Entity
public class Student{
@Id
private int id;
private String name;
private BigDecimal money;
@Version
private int version;
// getters and setters
}
对于实体中有添加了@Version
注解的列,JPA会自动对该实体使用乐观锁OPTIMISTIC_FORCE_INCREMENT
。你不需要使用任何锁命令。但是你也可以在Repository里面通过@Lock注解去自定义自己想要的锁。
使用乐观锁,打印sql,会发现在执行save方法的时候,会把version作为条件:
update ... whereid=? and version=?
所以乐观锁是Spring Data JPA在控制。你也可以不使用Spring Data JPA的乐观锁,而自己新增一个字段,在执行更新操作的时候手动去实现。
那OPTIMISTIC
与OPTIMISTIC_FORCE_INCREMENT
有什么区别呢?
OPTIMISTIC在读操作时不会更新Version字段的值,只有在写操作的时候会
OPTIMISTIC_FORCE_INCREMENT在读和写操作时都会更新Version字段的值
悲观锁是在数据库层面实现的
,JPA只是把请求交给数据库。悲观锁比较“悲观”,意思是只要开启事务的时候,就会对数据进行加锁操作,在事务结束后才解锁。而有些数据库是不支持PESSIMISTIC_READ
的,JPA规范中说到,不需要提供PESSIMISTIC_READ(因为许多DB只支持WRITE锁):
允许JPA实现用LockModeType.PESSIMISTIC_WRITE来代替LockModeType.PESSIMISTIC_READ,但是反之不可。
悲观写锁PESSIMISTIC_WRITE
是基于“SELECT … FOR UPDATE”这种SQL语法来实现的:
select
id
from
product
where
id =?
and version =? for update
悲观读锁PESSIMISTIC_READ
很多数据库不支持。在MYSQL中,会生成“SELECT …. LOCK INSHARE MODE”。
PESSIMISTIC_FORCE_INCREMENT
会使用悲观写锁,但不管有没有修改数据,都会更新Version字段的值。
@Lock
注解。否则会抛异常:// 尝试在非SELECT定义锁:
public interface ProductRepository extends JpaRepository{
@Modifying
@Query("update Product p set p.stock = p.stock + 1 where p.id = ?1")
@Lock(LockModeType.OPTIMISTIC)
Integer addStockById(Long id);
}
// 调用会抛出异常:
IllegalStateException: Illegal attempt to set lock mode on a non-SELECT query