Spring + JPA实现数据库读写分离

    本文展示了如何在Spring环境中使用JPA实现dataSource的读写分离(本文没有使用JTA事务),这个东西看起来简单,其实实现起来比较蹩脚,与JDBC有很大区别。

    1)使用Spring中的AbstractRoutingDataSource,辅助程序在运行时选择合适的dataSource。

    2)可以使用@Master、@Slave注释来强制dao方法调用必须使用master或者slave的数据库源。

    3)本例提供的ReadWriteDataSourceRouter可以根据当前Transaction的readOnly特性,将SQL调用按需分发给master或者slaves;可以指定多个slaves,可以简单的负载均衡。

 

1、persistence.xml

    如果我们不适用JTA事务的话,这个文件可以为空即可。

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
             xmlns="http://java.sun.com/xml/ns/persistence" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
             http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <!--
    <persistence-unit name="trial" transaction-type="RESOURCE_LOCAL">
    </persistence-unit>
    -->
</persistence>

 

2、ReadWriteDataSourceRouter.java

**
 * Created by liuguanqing on 16/5/10.
 * 全量读写分离
 */
public class ReadWriteDataSourceRouter extends AbstractRoutingDataSource {

    private Integer slaves;//slaves的个数

    private Random random = new Random();
    //如果基于JDK 7+,可以使用ThreadLocalRandom

    public void setSlaves(Integer slaves) {
        this.slaves = slaves;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
        if(!isReadOnly) {
            return "WRITE";
        }
        //如果是只读,可以从任意一个slave中执行
        return "READ_" + random.nextInt(slaves);
        //如果基于JDK 7+
        //ThreadLocalRandom random = ThreadLocalRandom.current();
    }
}

 

    java类中使用了一些约定的字符串,比如“WRITE”对应的为masterDataSource,所有的slaves对应的key必须为“READ_” + 数字。(参见下文配置)

 

3、spring-datasource.xml配置摘要:

<bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://192.168.1.100:3306/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;autoReconnect=false"></property>
    <property name="username" value="root"></property>
    <property name="password" value="root"></property>
    <property name="maxActive" value="128"></property>
    <property name="maxIdle" value="6"></property>
    <property name="minIdle" value="2"></property>
    <property name="maxWait" value="30000"></property>
    <property name="defaultAutoCommit" value="true"></property>
</bean>

<bean id="slaveDataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://192.168.1.101:3306/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;autoReconnect=false"></property>
    <property name="username" value="root"></property>
    <property name="password" value="root"></property>
    <property name="maxActive" value="128"></property>
    <property name="maxIdle" value="6"></property>
    <property name="minIdle" value="2"></property>
    <property name="maxWait" value="30000"></property>
    <property name="defaultAutoCommit" value="true"></property>
</bean>

<bean id="rwDataSource" class="com.test.demo.dataSource.TypedReadWriteDataSourceRouter">
    <property name="slaves" value="2"/><!-- 允许read操作的节点个数 -->
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="WRITE" value-ref="masterDataSource"/>
            <entry key="READ_0" value-ref="slaveDataSource" />
            <entry key="READ_1" value-ref="masterDataSource" /><!-- 允许部分read到slave上 -->
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="slaveDataSource"/><!-- or master -->
</bean>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceXmlLocation" value="classpath:persistence.xml" />
    <property name="dataSource" ref="rwDataSource" />
    <!-- model的package-->
    <property name="packagesToScan" value="com.test.demo.model"/>
    <property name="jpaVendorAdapter">
    	<!-- JPA的实现,有多种,请根据实际情况选择 -->
        <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
            <property name="showSql" value="false"/>
            <property name="generateDdl" value="false"/>
            <property name="database" value="MYSQL"/>
            <property name="databasePlatform" value="org.eclipse.persistence.platform.database.MySQLPlatform" />
        </bean>
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="eclipselink.weaving">false</prop>
            <prop key="eclipselink.cache.shared.default">false</prop>
            <prop key="eclipselink.read-only">true</prop>
        </props>
    </property>
</bean>


<!-- 声明一个Spring提供的JPA事务管理器,传入的参数是Spring中的实体管理器工厂 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<context:annotation-config/>
<tx:annotation-driven transaction-manager="transactionManager" />

 

4、TesUser.java(model样例) 

@Entity
@Table(name="test_user",schema = "vipkid")
public class TestUser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Column(name = "name")
    private String name;

    @Column(name = "password")
    private String password;

    // 创建时间
    @Temporal(TemporalType.DATE)
    @Column(name = "created")
    private Date created;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

 

5、TestUserRepository.java(DAO层)

@Repository
public class TestUserRepository  {

    @PersistenceContext
    private EntityManager entityManager;


    @Transactional(readOnly = true)
    //To slave
    public TestUser getFromSlave(int id) {
        String sql = "select T from TestUser T where T.id = :id";
        TypedQuery<TestUser> query = entityManager.createQuery(sql,TestUser.class);
        query.setParameter("id",id);
        return query.getSingleResult();
    }

    @Transactional(readOnly = false)
    //To master
    public TestUser getFromMaster(int id) {
        String sql = "select T from TestUser T where T.id = :id";
        TypedQuery<TestUser> query = entityManager.createQuery(sql,TestUser.class);
        query.setParameter("id",id);
        return query.getSingleResult();
    }
}

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐
  • —软件人才免语言低担保 赴美带薪读研!—



你可能感兴趣的:(spring,数据库,jpa)