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

标签: spring jpa 数据库 | 发表时间:2016-05-11 02:08 | 作者:
出处:http://shift-alt-ctrl.iteye.com

    本文展示了如何在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 数据库] 推荐:

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

- - 深入一点,你会更加快乐
    本文展示了如何在Spring环境中使用JPA实现dataSource的读写分离(本文没有使用JTA事务),这个东西看起来简单,其实实现起来比较蹩脚,与JDBC有很大区别.     1)使用Spring中的AbstractRoutingDataSource,辅助程序在运行时选择合适的dataSource.

Spring Data JPA 简单介绍

- tangfl - BlogJava-首页技术区
考虑到公司应用中数据库访问的多样性和复杂性,目前正在开发UDSL(统一数据访问层),开发到一半的时候,偶遇SpringData工程. 于是就花了点时间了解SpringData,可能UDSL II期会基于SpringData做扩展. 介绍:针对关系型数据库,KV数据库,Document数据库,Graph数据库,Map-Reduce等一些主流数据库,采用统一技术进行访问,并且尽可能简化访问手段.

spring data jpa简单实例

- - 编程语言 - ITeye博客
我们都知道Spring是一个非常优秀的JavaEE整合框架,它尽可能的减少我们开发的工作量和难度.   在持久层的业务逻辑方面,Spring开源组织又给我们带来了同样优秀的Spring Data JPA.   通常我们写持久层,都是先写一个接口,再写接口对应的实现类,在实现类中进行持久层的业务逻辑处理.

了解 Spring Data JPA - hungerW

- - 博客园_首页
自 JPA 伴随 Java EE 5 发布以来,受到了各大厂商及开源社区的追捧,各种商用的和开源的 JPA 框架如雨后春笋般出现,为开发者提供了丰富的选择. 它一改之前 EJB 2.x 中实体 Bean 笨重且难以使用的形象,充分吸收了在开源社区已经相对成熟的 ORM 思想. 另外,它并不依赖于 EJB 容器,可以作为一个独立的持久层技术而存在.

Spring Data JPA,基础学习笔记.

- - ITeye博客
最好先学习 JPA 方面的知识....在这里使用的是 Hibernate. 也已经使用了一段时间,看什么都不如看官方文档,我这里也只是写个笔记记录一下,如果能帮助到其他人,很开心 .算是个 demoshow 吧.这里也只写了一些我觉得比较有用的地方.其他一些使用知识,请参见官方文档:http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/.

Spring+MyBatis实践——MyBatis访问数据库

- - 开源软件 - ITeye博客
    在http://dufengx201406163237.iteye.com/blog/2102054中描述了工程的配置,在此记录一下如何使用MyBatis访问数据库;. . .

spring实现数据库读写分离

- - 行业应用 - ITeye博客
现在大型的电子商务系统,在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库. Master库负责数据更新和 实时数据查询,Slave库当然负责非实时数据查询. 因为在实际的应用中,数据库都是读多写少(读取数据的频率高,更新数据的频率相对较少),而读取数据 通常耗时比较长,占用数据库服务器的CPU较多,从而影响用户体验.

Spring AOP + Redis缓存数据库查询

- - 编程语言 - ITeye博客
我们希望能够将数据库查询结果缓存到Redis中,这样在第二次做同样的查询时便可以直接从redis取结果,从而减少数据库读写次数. 必须要做到与业务逻辑代码完全分离. 从缓存中读出的数据必须与数据库中的数据一致. 如何为一个数据库查询结果生成一个唯一的标识. Key),能唯一确定一个查询结果,同一个查询结果,一定能映射到同一个.

JPA & Hibernate 注解

- - CSDN博客编程语言推荐文章
必须,name为可选,对应数据库中一的个表 . 可选,通常和@Entity配合使用,只能标注在实体的class定义处,表示实体对应的数据库表的信息 . name:可选,表示表的名称.默认地,表名和实体名称一致,只有在不一致的情况下才需要指定表名 . catalog:可选,表示Catalog名称,默认为Catalog(""). .

在应用层通过spring解决数据库读写分离

- - CSDN博客推荐文章
如何配置mysql数据库的主从. 单机配置mysql主从: http://my.oschina.net/god/blog/496. 常见的解决数据库读写分离有两种方案. http://neoremind.net/2011/06/spring实现数据库读写分离. 目前的一些解决方案需要在程序中手动指定数据源,比较麻烦,后边我会通过AOP思想来解决这个问题.