SpringBootJpa整合MySQL和postgresql

2 篇文章 0 订阅
1 篇文章 0 订阅
本文详细介绍了如何在SpringBoot中整合JPA与多个数据源,包括MySQL和PostgreSQL,以及遇到的JPA自动注入方言错误和多数据源事务管理问题。通过自定义配置和AOP实现事务管理,解决了不同数据源的事务生效问题。
摘要由CSDN通过智能技术生成

maven配置

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

配置文件配置

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss.SSS
    time-zone: GMT+8
  datasource:
    primary:
#      type: com.alibaba.druid.pool.DruidDataSource
      jdbc-url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
      username : root
      password : XXXXXX
      driver-class-name: com.mysql.jdbc.Driver
    secondary:
      jdbc-url:  jdbc:postgresql://localhost:5432/test
      username: postgres
      password: XXXXXX
      driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      primary-dialect: org.hibernate.dialect.MySQL5InnoDBDialect
      secondary-dialect: org.hibernate.spatial.dialect.postgis.PostgisDialect
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        temp:
          use_jdbc_metadata_defaults: false

整合

数据源配置

@Configuration
public class DataSourceConfig {

    @Bean(name = "primaryDataSource")
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    @Primary
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondaryDataSource")
    @Qualifier("secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

}

MySQL整合到Jpa中

/**
 * 数据源一
 */
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "entityManagerFactoryPrimary",
        transactionManagerRef = "transactionManagerPrimary",
        basePackages = {
                "com.fyz.repository.mysql"

        }) //设置Repository所在位置

public class PrimaryConfig {


    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;

	//方言
    @Value("${spring.jpa.hibernate.primary-dialect}")
    private String primaryDialect;

    @Primary
    @Bean(name = "entityManagerPrimary")
    public EntityManager entityManager() {
        return entityManagerFactoryPrimary().getObject().createEntityManager();
    }

    @Primary
    @Bean(name = "entityManagerFactoryPrimary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary() {
    //定义数据库类型和连接方言等主要配置(不写两个数据库方言一样会报错)
        HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setGenerateDdl(true);
        jpaVendorAdapter.setDatabase(Database.MYSQL);
        jpaVendorAdapter.setDatabasePlatform(primaryDialect);
        LocalContainerEntityManagerFactoryBean builder = new LocalContainerEntityManagerFactoryBean();
        builder.setDataSource(primaryDataSource);
        builder.setPackagesToScan("com.fyz.entity.mysql");
        builder.setJpaVendorAdapter(jpaVendorAdapter);
        builder.setPersistenceUnitName("primaryPersistenceUnit");
        builder.setJpaPropertyMap(getVendorProperties());
        return builder;
    }


    @Autowired
    private JpaProperties jpaProperties;

    private Map getVendorProperties() {

        return jpaProperties.getHibernateProperties(new HibernateSettings());
    }

    @Primary
    @Bean(name = "transactionManagerPrimary")
    public PlatformTransactionManager transactionManagerPrimary() {
        return new JpaTransactionManager(entityManagerFactoryPrimary().getObject());
    }
}

psotgresql整合到Jpa中

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManager;
import javax.sql.DataSource;
import java.util.Map;

/**
 * 数据源二
 */
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "entityManagerFactorySecondary",
        transactionManagerRef = "transactionManagerSecondary",
        basePackages = {
                "com.fyz.repository.postgresql"
        }) //设置Repository所在位置,两个数据库对应的repository和实体类需要不同的路径
public class SecondaryConfig {

    //方言
    @Value("${spring.jpa.hibernate.secondary-dialect}")
    private String secondaryDialect;

    @Autowired
    @Qualifier("secondaryDataSource")
    private DataSource secondaryDataSource;

    @Bean(name = "entityManagerSecondary")
    public EntityManager entityManager() {
        return entityManagerFactorySecondary().getObject().createEntityManager();
    }

    /**
     * 将配置文件中对应的配置信息注册到jpa中进行管理
     * @return
     */
    @Bean(name = "entityManagerFactorySecondary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary() {
        HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setGenerateDdl(true);
        jpaVendorAdapter.setDatabase(Database.POSTGRESQL);
        jpaVendorAdapter.setDatabasePlatform(secondaryDialect);
        LocalContainerEntityManagerFactoryBean builder = new LocalContainerEntityManagerFactoryBean();
        builder.setDataSource(secondaryDataSource);
        builder.setPackagesToScan("com.fyz.entity.postgresql");
        builder.setJpaVendorAdapter(jpaVendorAdapter);
        builder.setPersistenceUnitName("secondaryPersistenceUnit");
        builder.setJpaPropertyMap(getVendorProperties());
        return builder;
    }

    @Autowired
    private JpaProperties jpaProperties;
    private Map getVendorProperties() {

        return jpaProperties.getHibernateProperties(new HibernateSettings());
    }

    //用来作为数据库事务回滚的限定词
    //@Transactional(rollbackFor = OAPMException.class, value = "transactionManagerSecondary")
    //事务管理器
    @Bean(name = "transactionManagerSecondary")
    PlatformTransactionManager transactionManagerSecondary() {
        return new JpaTransactionManager(entityManagerFactorySecondary().getObject());
    }
}

Jpa整合不同数据源遇到的问题

Jpa默认自动注入了方言,在相同数据源类型时没问题,但数据源不同时会启动报错。

我这里改成都自己注入配置。核心代码如下:

		HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setGenerateDdl(true);
        jpaVendorAdapter.setDatabase(Database.POSTGRESQL);
        jpaVendorAdapter.setDatabasePlatform(secondaryDialect);
        LocalContainerEntityManagerFactoryBean builder = new LocalContainerEntityManagerFactoryBean();
        builder.setDataSource(secondaryDataSource);
        builder.setPackagesToScan("com.fyz.entity.postgresql");
        builder.setJpaVendorAdapter(jpaVendorAdapter);
        builder.setPersistenceUnitName("secondaryPersistenceUnit");
        builder.setJpaPropertyMap(getVendorProperties());

多数据源同时访问时只有一个事物生效。

我通过AOP拦截然后手动开启事物,关闭事物,回滚事物解决的。思路如下:

  • 自定义事物注解
  • 拦截自定义的事物注解
  • 在方法调用前开启事物
  • 方法调用成提交事物\方法调用失败回滚事物
    自定义注解方法:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataSourceTransactions {
 
    /**
     * 事务管理器数组(默认管理所有的事物)
     */
    String[] transactionManagers() default {"transactionManagerPrimary","transactionManagerSecondary"};
}

AOP拦截方法:

@Component
@Aspect
@Slf4j
public class DataSourceTransactionAspect {
 
    /**
     * 线程本地变量:为什么使用栈?※为了达到后进先出的效果※
     */
    private static final ThreadLocal<Stack<Pair<PlatformTransactionManager, TransactionStatus>>> THREAD_LOCAL = new ThreadLocal<>();
 
    /**
     * 用于获取事务管理器
     */
    @Autowired
    private ApplicationContext applicationContext;
 
    /**
     * 事务声明
     */
    private DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    {
        // 非只读模式
        def.setReadOnly(false);
        // 事务隔离级别:采用数据库的
        def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
        // 事务传播行为
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    }
 
    /**
     * 切面
     */
    @Pointcut("@annotation(com.fyz.config.annotation.DataSourceTransactions)")
    public void pointcut() {
    }
 
    /**
     * 声明事务
     *
     * @param transactional 注解
     */
    @Before("pointcut() && @annotation(transactional)")
    public void before(DataSourceTransactions transactional) {
        // 根据设置的事务名称按顺序声明,并放到ThreadLocal里
        String[] transactionManagerNames = transactional.transactionManagers();
        //第一次拦截时创建,后面拦截时添加,防止嵌套调用多个事物时后面的事物删除前面的事物,导致方法添加失败
        Stack<Pair<PlatformTransactionManager, TransactionStatus>> pairStack = THREAD_LOCAL.get() ==null?new Stack<>():THREAD_LOCAL.get();
        for (String transactionManagerName : transactionManagerNames) {
            PlatformTransactionManager transactionManager = applicationContext.getBean(transactionManagerName, PlatformTransactionManager.class);
            TransactionStatus transactionStatus = transactionManager.getTransaction(def);
            pairStack.push(new Pair(transactionManager, transactionStatus));
        }
        THREAD_LOCAL.set(pairStack);
    }
 
    /**
     * 提交事务
     */
    @AfterReturning("pointcut()  && @annotation(transactional)")
    public void afterReturning(DataSourceTransactions transactional) {
        rollbackOrCommit(transactional,Boolean.TRUE);
    }
 
    /**
     * 回滚事务
     */
    @AfterThrowing(value = "pointcut()  && @annotation(transactional)")
    public void afterThrowing(DataSourceTransactions transactional) {
        rollbackOrCommit(transactional,Boolean.FALSE);
    }

    /**
     * 回滚或提交事物
     * @param transactional
     * @param isCommit
     */
    private void rollbackOrCommit(DataSourceTransactions transactional,Boolean isCommit){
        // ※栈顶弹出(后进先出)
        Stack<Pair<PlatformTransactionManager, TransactionStatus>> pairStack = THREAD_LOCAL.get();
        String[] transactionManagerNames = transactional.transactionManagers();
        for (int i = 0; i < transactionManagerNames.length; i++) {
            Pair<PlatformTransactionManager, TransactionStatus> pair = pairStack.pop();
            if(isCommit){
                pair.getKey().commit(pair.getValue());
            }else {
                pair.getKey().rollback(pair.getValue());}
        }
        //事物都消费完后删除线程,减少内存支出
        if(Objects.isNull(pairStack)|| pairStack.empty()){
            THREAD_LOCAL.remove();
        }
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值