将自定义注释建议应用于 spring 数据 jpa 存储库
Applying custom annotation advice to spring data jpa repository
我正在研究 mysql 主从复制。我正在使用 spring data jpa(spring boot).
我需要的是所有写操作都到主服务器,只读操作平均分配给多个只读从服务器。
为此我需要:
使用特殊的JDBC驱动程序:com.mysql.jdbc.ReplicationDriver
设置复制:在URL:
spring:
datasource:
driverClassName: com.mysql.jdbc.ReplicationDriver
url: jdbc:mysql:replication://127.0.0.1:3306,127.0.0.1:3307/MyForum?user=root&password=password&autoReconnect=true
test-on-borrow: true
validation-query: SELECT 1
database: MYSQL
需要关闭自动提交。 (*)
需要将连接设置为只读。
为了确保 JDBC 连接设置为只读,我创建了一个注解和一个简单的 AOP 拦截器。
注释
package com.xyz.forum.replication;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Bhupati Patel on 02/11/15.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ReadOnlyConnection {
}
拦截器
package com.xyz.forum.replication;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
/**
* Created by Bhupati Patel on 02/11/15.
*/
@Aspect
@Component
public class ConnectionInterceptor {
private Logger logger;
public ConnectionInterceptor() {
logger = LoggerFactory.getLogger(getClass());
logger.info("ConnectionInterceptor Started");
}
@Autowired
private EntityManager entityManager;
@Pointcut("@annotation(com.xyz.forum.replication.ReadOnlyConnection)")
public void inReadOnlyConnection(){}
@Around("inReadOnlyConnection()")
public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
Session session = entityManager.unwrap(Session.class);
ConnectionReadOnly readOnlyWork = new ConnectionReadOnly();
try{
session.doWork(readOnlyWork);
return pjp.proceed();
} finally {
readOnlyWork.switchBack();
}
}
}
以下是我的 spring 数据存储库
package com.xyz.forum.repositories;
import com.xyz.forum.entity.Topic;
import org.springframework.data.repository.Repository;
import java.util.List;
/**
* Created by Bhupati Patel on 16/04/15.
*/
public interface TopicRepository extends Repository<Topic,Integer>{
Topic save(Topic topic);
Topic findByTopicIdAndIsDeletedFalse(Integer topicId);
List<Topic> findByIsDeletedOrderByTopicOrderAsc(Boolean isDelete);
}
以下是我的经理(服务)class。
package com.xyz.forum.manager;
import com.xyz.forum.domain.entry.impl.TopicEntry;
import com.xyz.forum.domain.exception.impl.AuthException;
import com.xyz.forum.domain.exception.impl.NotFoundException;
import com.xyz.forum.entity.Topic;
import com.xyz.forum.replication.ReadOnlyConnection;
import com.xyz.forum.repositories.TopicRepository;
import com.xyz.forum.utils.converter.TopicConverter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
/**
* Created by Bhupati Patel on 16/04/15.
*/
@Repository
public class TopicManager {
@Autowired
TopicRepository topicRepository;
@Transactional
public TopicEntry save(TopicEntry topicEntry) {
Topic topic = TopicConverter.fromEntryToEntity(topicEntry);
return TopicConverter.fromEntityToEntry(topicRepository.save(topic));
}
@ReadOnlyConnection
public TopicEntry get(Integer id) {
Topic topicFromDb = topicRepository.findByTopicIdAndIsDeletedFalse(id);
if(topicFromDb == null) {
throw new NotFoundException("Invalid Id", "Topic Id [" + id + "] doesn't exist ");
}
return TopicConverter.fromEntityToEntry(topicFromDb);
}
}
在上面的代码中@ReadOnlyConnection注解是在manager或者service层指定的。 上面的代码对我来说工作正常。这是一个微不足道的案例,在服务层我只从从数据库读取并写入主数据库。
话虽如此,我的实际要求是我应该能够在存储库级别本身使用@ReadOnlyConnection,因为我有相当多的业务逻辑,我在其他 classes 中同时执行 read/write 操作服务层 layer.Therefore 我不能将@ReadOnlyConnection 放在服务层。
我应该可以用这样的东西
public interface TopicRepository extends Repository<Topic,Integer>{
Topic save(Topic topic);
@ReadOnlyConnection
Topic findByTopicIdAndIsDeletedFalse(Integer topicId);
@ReadOnlyConnection
List<Topic> findByIsDeletedOrderByTopicOrderAsc(Boolean isDelete);
}
喜欢spring的@Transactional 或@Modifying 或@Query 注释。以下是我所指的示例。
public interface AnswerRepository extends Repository<Answer,Integer> {
@Transactional
Answer save(Answer answer);
@Transactional
@Modifying
@Query("update Answer ans set ans.isDeleted = 1, ans.deletedBy = :deletedBy, ans.deletedOn = :deletedOn " +
"where ans.questionId = :questionId and ans.isDeleted = 0")
void softDeleteBulkAnswers(@Param("deletedBy") String deletedBy, @Param("deletedOn") Date deletedOn,
@Param("questionId") Integer questionId);
}
我是 aspectj 和 aop 世界的新手,我在 ConnectionInterceptor 中尝试了很多切入点正则表达式,但其中 none 有效。我已经尝试了很长时间,但还没有成功。
如何完成要求的任务。
我无法找到在方法级别使用我的自定义注释 @ReadOnlyConnection(如@Transactional)的解决方法,但一个小问题确实对我有用。
我正在粘贴下面的代码片段。
@Aspect
@Component
@EnableAspectJAutoProxy
public class ConnectionInterceptor {
private Logger logger;
private static final String JPA_PREFIX = "findBy";
private static final String CUSTOM_PREFIX = "read";
public ConnectionInterceptor() {
logger = LoggerFactory.getLogger(getClass());
logger.info("ConnectionInterceptor Started");
}
@Autowired
private EntityManager entityManager;
@Pointcut("this(org.springframework.data.repository.Repository)")
public void inRepositoryLayer() {}
@Around("inRepositoryLayer()")
public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
if (StringUtils.startsWith(methodName, JPA_PREFIX) || StringUtils.startsWith(methodName, CUSTOM_PREFIX)) {
System.out.println("I'm there!" );
Session session = entityManager.unwrap(Session.class);
ConnectionReadOnly readOnlyWork = new ConnectionReadOnly();
try{
session.doWork(readOnlyWork);
return pjp.proceed();
} finally {
readOnlyWork.switchBack();
}
}
return pjp.proceed();
}
}
所以在上面的代码中我使用了如下的切入点
@Pointcut("this(org.springframework.data.repository.Repository)")
public void inRepositoryLayer() {}
它的作用是
any join point (method execution only in Spring AOP) where the proxy implements the Repository interface
你可以看看
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html
现在我所有的存储库读取查询方法都以前缀 "findByXXX"(默认 spring-data-jpa 可读方法)或 "readXXX"(带有 @Query 注释的自定义读取方法)开头) 在我的 around 方法执行中与上述切入点匹配。根据我的要求,我将 JDBC Connection readOnly 设置为 true。
Session session = entityManager.unwrap(Session.class);
ConnectionReadOnly readOnlyWork = new ConnectionReadOnly();
我的 ConnectionReadOnly 如下所示
package com.xyz.forum.replication;
import org.hibernate.jdbc.Work;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Created by Bhupati Patel on 04/11/15.
*/
public class ConnectionReadOnly implements Work {
private Connection connection;
private boolean autoCommit;
private boolean readOnly;
@Override
public void execute(Connection connection) throws SQLException {
this.connection = connection;
this.autoCommit = connection.getAutoCommit();
this.readOnly = connection.isReadOnly();
connection.setAutoCommit(false);
connection.setReadOnly(true);
}
//method to restore the connection state before intercepted
public void switchBack() throws SQLException{
connection.setAutoCommit(autoCommit);
connection.setReadOnly(readOnly);
}
}
以上设置符合我的要求。
@Pointcut && @Around 似乎应该以如下方式声明:
@Pointcut(value = "execution(public * *(..))")
public void anyPublicMethod() {
}
@Around("@annotation(readOnlyConnection)")
我正在研究 mysql 主从复制。我正在使用 spring data jpa(spring boot).
我需要的是所有写操作都到主服务器,只读操作平均分配给多个只读从服务器。
为此我需要:
使用特殊的JDBC驱动程序:com.mysql.jdbc.ReplicationDriver
设置复制:在URL:
spring:
datasource:
driverClassName: com.mysql.jdbc.ReplicationDriver
url: jdbc:mysql:replication://127.0.0.1:3306,127.0.0.1:3307/MyForum?user=root&password=password&autoReconnect=true
test-on-borrow: true
validation-query: SELECT 1
database: MYSQL
需要关闭自动提交。 (*) 需要将连接设置为只读。
为了确保 JDBC 连接设置为只读,我创建了一个注解和一个简单的 AOP 拦截器。
注释
package com.xyz.forum.replication;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Bhupati Patel on 02/11/15.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ReadOnlyConnection {
}
拦截器
package com.xyz.forum.replication;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
/**
* Created by Bhupati Patel on 02/11/15.
*/
@Aspect
@Component
public class ConnectionInterceptor {
private Logger logger;
public ConnectionInterceptor() {
logger = LoggerFactory.getLogger(getClass());
logger.info("ConnectionInterceptor Started");
}
@Autowired
private EntityManager entityManager;
@Pointcut("@annotation(com.xyz.forum.replication.ReadOnlyConnection)")
public void inReadOnlyConnection(){}
@Around("inReadOnlyConnection()")
public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
Session session = entityManager.unwrap(Session.class);
ConnectionReadOnly readOnlyWork = new ConnectionReadOnly();
try{
session.doWork(readOnlyWork);
return pjp.proceed();
} finally {
readOnlyWork.switchBack();
}
}
}
以下是我的 spring 数据存储库
package com.xyz.forum.repositories;
import com.xyz.forum.entity.Topic;
import org.springframework.data.repository.Repository;
import java.util.List;
/**
* Created by Bhupati Patel on 16/04/15.
*/
public interface TopicRepository extends Repository<Topic,Integer>{
Topic save(Topic topic);
Topic findByTopicIdAndIsDeletedFalse(Integer topicId);
List<Topic> findByIsDeletedOrderByTopicOrderAsc(Boolean isDelete);
}
以下是我的经理(服务)class。
package com.xyz.forum.manager;
import com.xyz.forum.domain.entry.impl.TopicEntry;
import com.xyz.forum.domain.exception.impl.AuthException;
import com.xyz.forum.domain.exception.impl.NotFoundException;
import com.xyz.forum.entity.Topic;
import com.xyz.forum.replication.ReadOnlyConnection;
import com.xyz.forum.repositories.TopicRepository;
import com.xyz.forum.utils.converter.TopicConverter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
/**
* Created by Bhupati Patel on 16/04/15.
*/
@Repository
public class TopicManager {
@Autowired
TopicRepository topicRepository;
@Transactional
public TopicEntry save(TopicEntry topicEntry) {
Topic topic = TopicConverter.fromEntryToEntity(topicEntry);
return TopicConverter.fromEntityToEntry(topicRepository.save(topic));
}
@ReadOnlyConnection
public TopicEntry get(Integer id) {
Topic topicFromDb = topicRepository.findByTopicIdAndIsDeletedFalse(id);
if(topicFromDb == null) {
throw new NotFoundException("Invalid Id", "Topic Id [" + id + "] doesn't exist ");
}
return TopicConverter.fromEntityToEntry(topicFromDb);
}
}
在上面的代码中@ReadOnlyConnection注解是在manager或者service层指定的。 上面的代码对我来说工作正常。这是一个微不足道的案例,在服务层我只从从数据库读取并写入主数据库。
话虽如此,我的实际要求是我应该能够在存储库级别本身使用@ReadOnlyConnection,因为我有相当多的业务逻辑,我在其他 classes 中同时执行 read/write 操作服务层 layer.Therefore 我不能将@ReadOnlyConnection 放在服务层。
我应该可以用这样的东西
public interface TopicRepository extends Repository<Topic,Integer>{
Topic save(Topic topic);
@ReadOnlyConnection
Topic findByTopicIdAndIsDeletedFalse(Integer topicId);
@ReadOnlyConnection
List<Topic> findByIsDeletedOrderByTopicOrderAsc(Boolean isDelete);
}
喜欢spring的@Transactional 或@Modifying 或@Query 注释。以下是我所指的示例。
public interface AnswerRepository extends Repository<Answer,Integer> {
@Transactional
Answer save(Answer answer);
@Transactional
@Modifying
@Query("update Answer ans set ans.isDeleted = 1, ans.deletedBy = :deletedBy, ans.deletedOn = :deletedOn " +
"where ans.questionId = :questionId and ans.isDeleted = 0")
void softDeleteBulkAnswers(@Param("deletedBy") String deletedBy, @Param("deletedOn") Date deletedOn,
@Param("questionId") Integer questionId);
}
我是 aspectj 和 aop 世界的新手,我在 ConnectionInterceptor 中尝试了很多切入点正则表达式,但其中 none 有效。我已经尝试了很长时间,但还没有成功。
如何完成要求的任务。
我无法找到在方法级别使用我的自定义注释 @ReadOnlyConnection(如@Transactional)的解决方法,但一个小问题确实对我有用。
我正在粘贴下面的代码片段。
@Aspect
@Component
@EnableAspectJAutoProxy
public class ConnectionInterceptor {
private Logger logger;
private static final String JPA_PREFIX = "findBy";
private static final String CUSTOM_PREFIX = "read";
public ConnectionInterceptor() {
logger = LoggerFactory.getLogger(getClass());
logger.info("ConnectionInterceptor Started");
}
@Autowired
private EntityManager entityManager;
@Pointcut("this(org.springframework.data.repository.Repository)")
public void inRepositoryLayer() {}
@Around("inRepositoryLayer()")
public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
if (StringUtils.startsWith(methodName, JPA_PREFIX) || StringUtils.startsWith(methodName, CUSTOM_PREFIX)) {
System.out.println("I'm there!" );
Session session = entityManager.unwrap(Session.class);
ConnectionReadOnly readOnlyWork = new ConnectionReadOnly();
try{
session.doWork(readOnlyWork);
return pjp.proceed();
} finally {
readOnlyWork.switchBack();
}
}
return pjp.proceed();
}
}
所以在上面的代码中我使用了如下的切入点
@Pointcut("this(org.springframework.data.repository.Repository)")
public void inRepositoryLayer() {}
它的作用是
any join point (method execution only in Spring AOP) where the proxy implements the Repository interface
你可以看看 http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html
现在我所有的存储库读取查询方法都以前缀 "findByXXX"(默认 spring-data-jpa 可读方法)或 "readXXX"(带有 @Query 注释的自定义读取方法)开头) 在我的 around 方法执行中与上述切入点匹配。根据我的要求,我将 JDBC Connection readOnly 设置为 true。
Session session = entityManager.unwrap(Session.class);
ConnectionReadOnly readOnlyWork = new ConnectionReadOnly();
我的 ConnectionReadOnly 如下所示
package com.xyz.forum.replication;
import org.hibernate.jdbc.Work;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Created by Bhupati Patel on 04/11/15.
*/
public class ConnectionReadOnly implements Work {
private Connection connection;
private boolean autoCommit;
private boolean readOnly;
@Override
public void execute(Connection connection) throws SQLException {
this.connection = connection;
this.autoCommit = connection.getAutoCommit();
this.readOnly = connection.isReadOnly();
connection.setAutoCommit(false);
connection.setReadOnly(true);
}
//method to restore the connection state before intercepted
public void switchBack() throws SQLException{
connection.setAutoCommit(autoCommit);
connection.setReadOnly(readOnly);
}
}
以上设置符合我的要求。
@Pointcut && @Around 似乎应该以如下方式声明:
@Pointcut(value = "execution(public * *(..))")
public void anyPublicMethod() {
}
@Around("@annotation(readOnlyConnection)")