1

私は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();
        }
    }

}

以下は私の春のデータリポジトリです

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);

}

以下は私の Manager(Service) クラスです。

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 アノテーションがマネージャーまたはサービス層で指定されています。上記のコードは私にとってはうまくいきます。サービスレイヤーでスレーブデータベースから読み取り、マスターデータベースに書き込むだけの簡単なケースです。

私の実際の要件は、リポジトリレベル自体で @ReadOnlyConnection を使用できる必要があると言っていました。これは、サービスレイヤーの他のクラスで読み取り/書き込み操作の両方を行うビジネスロジックがかなりあるためです。したがって、 @ReadOnlyConnection を配置することはできませんサービス層で。

私はこのようなものを使用できるはずです

public interface TopicRepository extends Repository<Topic,Integer>{
    Topic save(Topic topic);
    @ReadOnlyConnection
    Topic findByTopicIdAndIsDeletedFalse(Integer topicId);
    @ReadOnlyConnection
    List<Topic> findByIsDeletedOrderByTopicOrderAsc(Boolean isDelete);

}

春の @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でかなりの数のポイントカット正規表現を試しましたが、どれも機能しませんでした。私は長い間これを試してきましたが、まだ運がありません。

求められたタスクを達成する方法。

4

2 に答える 2

5

カスタムアノテーション @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() {}

そしてそれがすることは

プロキシが Repository インターフェースを実装する任意の結合ポイント (Spring AOP でのみメソッド実行)

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.htmlでご覧いただけます。

現在、すべてのリポジトリ読み取りクエリメソッドは、プレフィックス「findByXXX」(デフォルトの spring-data-jpa 読み取り可能メソッド)または「readXXX」(@Query アノテーションを使用したカスタム読み取りメソッド)のいずれかで始まります。これらは、上記のポイントカットによって一致するメソッド実行で一致します。私の要件によると、JDBC接続の読み取り専用を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);
    }
}

したがって、上記の設定は私の要件で機能します。

于 2015-11-17T10:48:55.410 に答える
0

@Pointcut && @Around は、次のような方法で宣言する必要があるようです。

@Pointcut(value = "execution(public * *(..))")
public void anyPublicMethod() {
}

@Around("@annotation(readOnlyConnection)")
于 2020-09-14T16:26:15.887 に答える