当用了事务后,用jpa从数据库中查出来的实体对象,直接变更实体对象属性值,即使不调用repository.save()方法也会自动更新入库,下面讲讲怎么利用切面编来关闭JPA的自动更新入库
pom文件
4.0.0
com.flynn
study
0.0.1-SNAPSHOT
com.flynn
jpa-demo
0.0.1-SNAPSHOT
jpa-demo
http://maven.apache.org
UTF-8
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
mysql
mysql-connector-java
runtime
com.alibaba
druid
org.projectlombok
lombok
junit
junit
org.springframework.boot
spring-boot-maven-plugin
用到的语句
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint NOT NULL,
`address` varchar(255) DEFAULT NULL,
`age` int DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`gender` smallint NOT NULL,
`name` varchar(255) DEFAULT NULL,
`nick_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, '广州市', 16, '[email protected]', 1, 'test01', '天上人间00');
INSERT INTO `user` VALUES (2, '广州市', 18, '[email protected]', 2, 'test02', '天上人间00');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
定义实体
package org.jpa.demo.entity;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@Entity
public class User implements Serializable {
private static final long serialVersionUID = 7940549851075553325L;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String nickName;
private Integer age;
private String email;
private short gender;
private String address;
}
repository定义
package org.jpa.demo.repository;
import java.util.List;
import org.jpa.demo.entity.User;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends CrudRepository{
@Query(value = "select * from user", nativeQuery = true)
List getUsers();
}
MyRunner实现接口CommandLineRunner
启动服务会执行run()函数里的逻辑
package org.jpa.demo.runner;
import java.util.List;
import java.util.Optional;
import javax.transaction.Transactional;
import org.jpa.demo.ResponseData;
import org.jpa.demo.entity.User;
import org.jpa.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class MyRunner implements CommandLineRunner{
@Autowired
private UserRepository userRepository;
@Transactional
@Override
public void run(String... args) throws Exception {
log.info("MyRunner ... ... start");
log.info("userRepository.getUsers:");
List users = userRepository.getUsers();
log.info("update:");
users.forEach(user->{
String beforeUser = user.toString();
user.setName(user.getName()+"-u");
String afterUser = user.toString();
log.info("update: {} => {}",beforeUser,afterUser);
});
log.info("MyRunner ... ... end");
}
}
启动类
package org.jpa.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JpaDemoApplication
{
public static void main( String[] args )
{
SpringApplication.run(JpaDemoApplication.class, args);
}
}
启动服务,看看效果
- 控制台日志
2022-09-17 17:17:33.248 INFO 94326 --- [ main] org.jpa.demo.JpaDemoApplication : Started JpaDemoApplication in 4.279 seconds (JVM running for 5.332)
2022-09-17 17:17:33.269 INFO 94326 --- [ main] org.jpa.demo.runner.MyRunner : MyRunner ... ... start
2022-09-17 17:17:33.269 INFO 94326 --- [ main] org.jpa.demo.runner.MyRunner : userRepository.getUsers:
Hibernate: select * from user
2022-09-17 17:17:33.340 INFO 94326 --- [ main] org.jpa.demo.runner.MyRunner : update:
2022-09-17 17:17:33.340 INFO 94326 --- [ main] org.jpa.demo.runner.MyRunner : update: User(id=1, name=test01, nickName=天上人间00, age=16, [email protected], gender=1, address=广州市) => User(id=1, name=test01-u, nickName=天上人间00, age=16, [email protected], gender=1, address=广州市)
2022-09-17 17:17:33.340 INFO 94326 --- [ main] org.jpa.demo.runner.MyRunner : update: User(id=2, name=test02, nickName=天上人间00, age=18, [email protected], gender=2, address=广州市) => User(id=2, name=test02-u, nickName=天上人间00, age=18, [email protected], gender=2, address=广州市)
2022-09-17 17:17:33.340 INFO 94326 --- [ main] org.jpa.demo.runner.MyRunner : MyRunner ... ... end
Hibernate: update user set address=?, age=?, email=?, gender=?, name=?, nick_name=? where id=?
Hibernate: update user set address=?, age=?, email=?, gender=?, name=?, nick_name=? where id=?
从日志上看有执行update,更新了表里的数据
- 表里的数据
1 广州市 16 [email protected] 1 test01-u 天上人间00
2 广州市 18 [email protected] 2 test02-u 天上人间00
看表里的数据,确实把表里的数据更新了,而代码里并没有调用类似repository.save()这样的方法来更新入库,JPA自动入库了
为了让JPA不自动入库,这里定义一个切面来处理
- 定义个切面
package org.jpa.demo.aspect;
import java.util.Iterator;
import java.util.Optional;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Aspect
@Component
public class RepositoryAspect {
@Autowired
private EntityManager entityManager;
@Pointcut("execution(public * org.jpa.demo.repository..*.*(..))*")
public void repositoryEvict() {}
// @After("repositoryEvict()")
// public void doAfter() {
//// entityManager.flush();
// entityManager.clear();
// log.info("Aspect repositoryEvict() doAfter ... ...");
// }
@Around("repositoryEvict()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
clear(result);
log.info("Aspect repositoryEvict(),{}",joinPoint.getSignature());
return result;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void clear(Object result) {
if (result.getClass().getAnnotation(Entity.class)!=null) {
evict(result);
return;
}
if (result instanceof Optional) {
Object entity = ((Optional)result).orElse(null);
if(entity!=null && entity.getClass().getAnnotation(Entity.class)!=null) {
evict(entity);
}
return;
}
if ((result instanceof Iterable)) {
Iterator iterator = ((Iterable)result).iterator();
boolean init = false;
boolean toClear = false;
while (iterator.hasNext()) {
Object object = (Object) iterator.next();
if (!init) {
toClear = object.getClass().getAnnotation(Entity.class)!= null;
}
if (!toClear) {
return;
}
evict(object);
}
return;
}
}
private void evict(Object entity) {
Session session = (Session)entityManager.getDelegate();
if (session.isOpen()) {
session.evict(entity);
}
}
}
- 看看效果
日志
2022-09-17 17:24:11.576 INFO 97485 --- [ main] org.jpa.demo.runner.MyRunner : MyRunner ... ... start
2022-09-17 17:24:11.576 INFO 97485 --- [ main] org.jpa.demo.runner.MyRunner : userRepository.getUsers:
Hibernate: select * from user
2022-09-17 17:24:11.653 INFO 97485 --- [ main] org.jpa.demo.aspect.RepositoryAspect : Aspect repositoryEvict(),List org.jpa.demo.repository.UserRepository.getUsers()
2022-09-17 17:24:11.653 INFO 97485 --- [ main] org.jpa.demo.runner.MyRunner : update:
2022-09-17 17:24:11.653 INFO 97485 --- [ main] org.jpa.demo.runner.MyRunner : update: User(id=1, name=test01-u, nickName=天上人间00, age=16, [email protected], gender=1, address=广州市) => User(id=1, name=test01-u-u, nickName=天上人间00, age=16, [email protected], gender=1, address=广州市)
2022-09-17 17:24:11.653 INFO 97485 --- [ main] org.jpa.demo.runner.MyRunner : update: User(id=2, name=test02-u, nickName=天上人间00, age=18, [email protected], gender=2, address=广州市) => User(id=2, name=test02-u-u, nickName=天上人间00, age=18, [email protected], gender=2, address=广州市)
2022-09-17 17:24:11.654 INFO 97485 --- [ main] org.jpa.demo.runner.MyRunner : MyRunner ... ... end
日志里有更新user.name属性,逻辑上有改变属性值(没有调用repository.save()进行入库)
test01-u => test01-u-u
test02-u => test02-u-u
表里的数据
1 广州市 16 [email protected] 1 test01-u 天上人间00
2 广州市 18 [email protected] 2 test02-u 天上人间00
从表的数据可以看出,确实没有入库,这样就可以关闭spring-data-jpa的自动更新入库