SpringBoot集成Spring Data JPA及读写分离

相关代码: github OSCchina

JPA是什么

JPA(Java Persistence API)是Sun官方提出的Java持久化规范,它为Java开发人员提供了一种对象/关联映射工具 来管理Java应用中的关系数据.它包括以下几方面的内容:

1.ORM映射 支持xml和注解方式建立实体与表之间的映射.

2.Java持久化API 定义了一些常用的CRUD接口,我们只需直接调用,而不需要考虑底层JDBC和SQL的细节.

3.JPQL查询语言 这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合.

在工作中,我们都会用到ORM技术,比如Hibernate,JOOQ等,根据需求的不同,我们会采用不同的ORM框架,当我们需要 更换ORM框架来满足我们的需求时,由于不同ORM框架的实现,使用方式的区别以及各自为营,我们往往需要对代码进行重构.JPA的 出现就是为了解决这个问题,JPA充分吸收了现有一些ORM框架的优点,具有易于使用,伸缩性强等优点,为ORM技术提供了一套标准的 接口用来整合不同的ORM框架.

Hibernate对JPA的实现

JPA本身并不做具体的实现,而只是定义了一些接口规范,让其它ORM来具体的实现这些接口,就目前来说,对JPA规范实现最好的就是 Hibernate了.这里提一下Mybatis,Mybatis并没有实现JPA规范,它本身也不能算做一个真正的ORM框架.

Spring Data JPA是什么

Spring Data JPA只是Spring Data框架的一个模块,可以极大的简化JPA的使用,Spring Data JPA强大的地方还在于能够简化我们 对持久层业务逻辑的开发,通过规范持久层方法的名称,通过名称来判断需要实现什么业务逻辑,我们机会可以在不写一句sql,不做任何dao层 逻辑的情况下完成我们绝大部分的开发,当然,对于一些复杂的,性能要求高的查询,Spring Data JPA一样支持我们使用原生的sql.

在这里我们不过多的去介绍JPA以及Spring Data JPA,主要还是与SpringBoot集成的一些细节以及示例.

引入依赖

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

我们引入这个依赖后,发现也引入了Hibernate的包,这是现在一种默认的做法,Hibernate已经被作为JPA规范的最好实现了,这里就不介绍Druid数据源的 配置了,大家可以看另外一篇XXXX.

配置我们的数据源以及JPA(Hibernate)

#配置模板
#https://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/html/common-application-properties.html
#数据源
spring.datasource.druid.write.url=jdbc:mysql://localhost:3306/jpa
spring.datasource.druid.write.username=root
spring.datasource.druid.write.password=1
spring.datasource.druid.write.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.read.url=jdbc:mysql://localhost:3306/jpa
spring.datasource.druid.read.username=root
spring.datasource.druid.read.password=1
spring.datasource.druid.read.driver-class-name=com.mysql.jdbc.Driver
#JPA (JpaBaseConfiguration, HibernateJpaAutoConfiguration)
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.database=mysql
spring.jpa.generate-ddl=true
#就是hibernate.hbm2ddl.auto,具体说明可以看README
spring.jpa.hibernate.ddl-auto=update
#通过方法名解析sql的策略,具体说明可以看README,这里就不配置了
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy
spring.jpa.show-sql=true
#spring.jpa.properties.*
#spring.jpa.properties.hibernate.hbm2ddl.auto=update
#spring.jpa.properties.hibernate.show_sql=true
#spring.jpa.properties.hibernate.use-new-id-generator-mappings=true

druid数据源注入

@Configuration
public class DruidDataSourceConfig {
  /**
   * DataSource 配置
   * @return
   */
  @ConfigurationProperties(prefix = "spring.datasource.druid.read")
  @Bean(name = "readDruidDataSource")
  public DataSource readDruidDataSource() {
    return new DruidDataSource();
  }
  /**
   * DataSource 配置
   * @return
   */
  @ConfigurationProperties(prefix = "spring.datasource.druid.write")
  @Bean(name = "writeDruidDataSource")
  @Primary
  public DataSource writeDruidDataSource() {
    return new DruidDataSource();
  }
}

EntityManagerFactory实例注入

EntityManagerFactory类似于Hibernate的SessionFactory,mybatis的SqlSessionFactory 总之,在执行操作之前,我们总要获取一个EntityManager,这就类似于Hibernate的Session, mybatis的sqlSession. 注入EntityManagerFactory有两种方式,一种是直接注入EntityManagerFactory,另一种是通过 LocalContainerEntityManagerFactoryBean来间接注入.虽说这两种方法都是基于 LocalContainerEntityManagerFactoryBean的,但是在配置上还是有一些区别.

1.直接注入EntityManagerFactory

配置:通过spring.jpa.properties.*来配置Hibernate的属性

spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use-new-id-generator-mappings=true
@Configuration
@EnableJpaRepositories(value = "com.lc.springBoot.jpa.repository",
            entityManagerFactoryRef = "writeEntityManagerFactory",
            transactionManagerRef="writeTransactionManager")
public class WriteDataSourceConfig {
  @Autowired
  JpaProperties jpaProperties;
  @Autowired
  @Qualifier("writeDruidDataSource")
  private DataSource writeDruidDataSource;
  /**
   * EntityManagerFactory类似于Hibernate的SessionFactory,mybatis的SqlSessionFactory
   * 总之,在执行操作之前,我们总要获取一个EntityManager,这就类似于Hibernate的Session,
   * mybatis的sqlSession.
   * @return
   */
  @Bean(name = "writeEntityManagerFactory")
  @Primary
  public EntityManagerFactory writeEntityManagerFactory() {
    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    factory.setJpaVendorAdapter(vendorAdapter);
    factory.setPackagesToScan("com.lc.springBoot.jpa.entity");
    factory.setDataSource(writeDruidDataSource);//数据源
    factory.setJpaPropertyMap(jpaProperties.getProperties());
    factory.afterPropertiesSet();//在完成了其它所有相关的配置加载以及属性设置后,才初始化
    return factory.getObject();
  }
  /**
   * 配置事物管理器
   * @return
   */
  @Bean(name = "writeTransactionManager")
  @Primary
  public PlatformTransactionManager writeTransactionManager() {
    JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
    jpaTransactionManager.setEntityManagerFactory(this.writeEntityManagerFactory());
    return jpaTransactionManager;
  }
}

2.先注入LocalContainerEntityManagerFactoryBean,再获取EntityManagerFactory

配置:

spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.database=mysql
spring.jpa.generate-ddl=true
#就是hibernate.hbm2ddl.auto,具体说明可以看README
spring.jpa.hibernate.ddl-auto=update
#通过方法名解析sql的策略,具体说明可以看README,这里就不配置了
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy
spring.jpa.show-sql=true
@Configuration
@EnableJpaRepositories(value = "com.lc.springBoot.jpa.repository",
    entityManagerFactoryRef = "writeEntityManagerFactory",
    transactionManagerRef = "writeTransactionManager")
public class WriteDataSourceConfig1 {
  @Autowired
  JpaProperties jpaProperties;
  @Autowired
  @Qualifier("writeDruidDataSource")
  private DataSource writeDruidDataSource;
  /**
   * 我们通过LocalContainerEntityManagerFactoryBean来获取EntityManagerFactory实例
   * @return
   */
  @Bean(name = "writeEntityManagerFactoryBean")
  @Primary
  public LocalContainerEntityManagerFactoryBean writeEntityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
    return builder
        .dataSource(writeDruidDataSource)
        .properties(jpaProperties.getProperties())
        .packages("com.lc.springBoot.jpa.entity") //设置实体类所在位置
        .persistenceUnit("writePersistenceUnit")
        .build();
    //.getObject();//不要在这里直接获取EntityManagerFactory
  }
  /**
   * EntityManagerFactory类似于Hibernate的SessionFactory,mybatis的SqlSessionFactory
   * 总之,在执行操作之前,我们总要获取一个EntityManager,这就类似于Hibernate的Session,
   * mybatis的sqlSession.
   * @param builder
   * @return
   */
  @Bean(name = "writeEntityManagerFactory")
  @Primary
  public EntityManagerFactory writeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
    return this.writeEntityManagerFactoryBean(builder).getObject();
  }
  /**
   * 配置事物管理器
   * @return
   */
  @Bean(name = "writeTransactionManager")
  @Primary
  public PlatformTransactionManager writeTransactionManager(EntityManagerFactoryBuilder builder) {
    return new JpaTransactionManager(writeEntityManagerFactory(builder));
  }
}

对于这个配置

@Bean(name = "writeEntityManagerFactoryBean")
  @Primary
  public LocalContainerEntityManagerFactoryBean writeEntityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
    return builder
        .dataSource(writeDruidDataSource)
        .properties(jpaProperties.getProperties())
        .packages("com.lc.springBoot.jpa.entity") //设置实体类所在位置
        .persistenceUnit("writePersistenceUnit")
        .build();
    //.getObject();//不要在这里直接获取EntityManagerFactory
  }

getObject()方法可以获取到EntityManagerFactory的实例,看似跟第一种没有什么区别,但是我们不能直接用 getObject(),不然会获取不到,报空指针异常.

读写分离配置

自定义注入AbstractRoutingDataSource

@Configuration
public class DataSourceConfig {
  private final static String WRITE_DATASOURCE_KEY = "writeDruidDataSource";
  private final static String READ_DATASOURCE_KEY = "readDruidDataSource";
  /**
   * 注入AbstractRoutingDataSource
   * @param readDruidDataSource
   * @param writeDruidDataSource
   * @return
   * @throws Exception
   */
  @Bean
  public AbstractRoutingDataSource routingDataSource(
      @Qualifier(READ_DATASOURCE_KEY) DataSource readDruidDataSource,
      @Qualifier(WRITE_DATASOURCE_KEY) DataSource writeDruidDataSource
  ) throws Exception {
    DynamicDataSource dataSource = new DynamicDataSource();
    Map<Object, Object> targetDataSources = new HashMap();
    targetDataSources.put(WRITE_DATASOURCE_KEY, writeDruidDataSource);
    targetDataSources.put(READ_DATASOURCE_KEY, readDruidDataSource);
    dataSource.setTargetDataSources(targetDataSources);
    dataSource.setDefaultTargetDataSource(writeDruidDataSource);
    return dataSource;
  }
}

自定义注解

  @Target({ElementType.METHOD, ElementType.TYPE})
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  public @interface TargetDataSource {
    String dataSource() default "";//数据源
  }

使用ThreadLocal使数据源与线程绑定

 public class DynamicDataSourceHolder {
    //使用ThreadLocal把数据源与当前线程绑定
    private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
    public static void setDataSource(String dataSourceName) {
      dataSources.set(dataSourceName);
    }
    public static String getDataSource() {
      return (String) dataSources.get();
    }
    public static void clearDataSource() {
      dataSources.remove();
    }
  }
  public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
      //可以做一个简单的负载均衡策略
      String lookupKey = DynamicDataSourceHolder.getDataSource();
      System.out.println("------------lookupKey---------"+lookupKey);
      return lookupKey;
    }
  }

定义切面

@Aspect
  @Component
  public class DynamicDataSourceAspect {
    @Around("execution(public * com.lc.springBoot.jpa.service..*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
      MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
      Method targetMethod = methodSignature.getMethod();
      if (targetMethod.isAnnotationPresent(TargetDataSource.class)) {
        String targetDataSource = targetMethod.getAnnotation(TargetDataSource.class).dataSource();
        System.out.println("----------数据源是:" + targetDataSource + "------");
        DynamicDataSourceHolder.setDataSource(targetDataSource);
      }
      Object result = pjp.proceed();//执行方法
      DynamicDataSourceHolder.clearDataSource();
      return result;
    }
  }

以上所述是小编给大家介绍的SpringBoot集成Spring Data JPA及读写分离,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

时间: 2017-04-17

Spring Data JPA实现动态查询的两种方法

前言 一般在写业务接口的过程中,很有可能需要实现可以动态组合各种查询条件的接口.如果我们根据一种查询条件组合一个方法的做法来写,那么将会有大量方法存在,繁琐,维护起来相当困难.想要实现动态查询,其实就是要实现拼接SQL语句.无论实现如何复杂,基本都是包括select的字段,from或者join的表,where或者having的条件.在Spring Data JPA有两种方法可以实现查询条件的动态查询,两种方法都用到了Criteria API. Criteria API 这套API可用于构建对数据

Spring Boot中使用Spring-data-jpa实现数据库增删查改

在实际开发过程中,对数据库的操作无非就"增删改查".就最为普遍的单表操作而言,除了表和字段不同外,语句都是类似的,开发人员需要写大量类似而枯燥的语句来完成业务逻辑. 为了解决这些大量枯燥的数据操作语句,我们第一个想到的是使用ORM框架,比如:Hibernate.通过整合Hibernate之后,我们以操作Java实体的方式最终将数据改变映射到数据库表中. 为了解决抽象各个Java实体基本的"增删改查"操作,我们通常会以泛型的方式封装一个模板Dao来进行抽象简化,但是这

Spring Data JPA 简单查询--方法定义规则(详解)

一.常用规则速查 1 And 并且 2 Or   或 3 Is,Equals 等于 4 Between   两者之间 5 LessThan 小于 6 LessThanEqual   小于等于 7 GreaterThan 大于 8 GreaterThanEqual   大于等于 9 After 之后(时间) > 10 Before 之前(时间) < 11 IsNull 等于Null 12 IsNotNull,NotNull 不等于Null 13 Like 模糊查询.查询件中需要自己加 % 14

springboot整合spring-data-redis遇到的坑

描述 使用springboot整合redis,使用默认的序列化配置,然后使用redis-client去查询时查询不到相应的key. 使用工具发现,key的前面多了\xAC\xED\x00\x05t\x00!这样一个串. 而且value也是不能直观可见的. 问题所在 使用springdataredis,默认情况下是使用org.springframework.data.redis.serializer.JdkSerializationRedisSerializer这个类来做序列化. org.spri

详解Spring data 定义默认时间与日期的实例

详解Spring data 定义默认时间与日期的实例 前言: 需求是这样的: 1. 创建时间与更新时间只能由数据库产生,不允许在实体类中产生,因为每个节点的时间/时区不一定一直.另外防止人为插入自定义时间时间. 2. 插入记录的时候创建默认时间,创建时间不能为空,时间一旦插入不允许日后在实体类中修改. 3. 记录创建后更新日志字段为默认为 null 表示该记录没有被修改过.一旦数据被修改,修改日期字段将记录下最后的修改时间. 4. 甚至你可以通过触发器实现一个history 表,用来记录数据的历

基于SpringMVC+Bootstrap+DataTables实现表格服务端分页、模糊查询

前言 基于SpringMVC+Bootstrap+DataTables实现数据表格服务端分页.模糊查询(非DataTables Search),页面异步刷新. 说明:sp:message标签是使用了SpringMVC国际化 效果 DataTable表格 关键字查询 自定义关键字查询,非DataTable Search 代码 HTML代码 查询条件代码 <!-- 查询.添加.批量删除.导出.刷新 --> <div class="row-fluid"> <di

ThinkPHP整合datatables实现服务端分页的示例代码

最近做东西有一个需求,因为数据量很大,在这里我决定使用datatables的服务端分页,同时还需要传递查询条件到服务端.在网上搜索的大部分文章都感觉有些误差,于是自己封装了一下,主要配置/工具为: 服务端:php(使用thinkphp) 页面样式来自于H-ui框架(datatables版本为1.10.0) 主要修改(databases)配置项为: 1) bProcessing:true 使用ajax源 2) serverSide:true 使用服务端分页 3) createdRow:functi

Python+Socket实现基于TCP协议的客户与服务端中文自动回复聊天功能示例

本文实例讲述了Python+Socket实现基于TCP协议的客户与服务端中文自动回复聊天功能.分享给大家供大家参考,具体如下: [吐槽] 网上的代码害死人,看着都写的言之凿凿,可运行就是有问题. 有些爱好代码.喜欢收藏代码的朋友,看到别人的代码就粘贴复制过来.可是起码你也试试运行看啊大哥 [正文] 昨日修改运行了UDP协议的C/S聊天程序,可是TCP协议的怎么都不行.各种试,各种坑. 做了下面几个修改后,终于可以了: 1.对发送.接收的信息,分别进行编码和解码 2.客户端的第10行bind改为c

Java基于socket实现的客户端和服务端通信功能完整实例

本文实例讲述了Java基于socket实现的客户端和服务端通信功能.分享给大家供大家参考,具体如下: 以下代码参考马士兵的聊天项目,先运行ChatServer.java实现端口监听,然后再运行ChatClient.java 客户端实例 ChatClient.java package socketDemo; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; public class Ch

jquery datatable服务端分页

OK,上次完成了客户端的分页,这次我们就在上一次的Demo上进行修改,来实现服务端的分页~ js代码: <script type="text/javascript"> $(document).ready(function() { $('#table_id_example').DataTable({ "bProcessing" : false, //是否显示加载 "sAjaxSource" : '/datatableDemo/user/

Bootstrap Table使用整理(五)之分页组合查询

推荐阅读: Bootstrap Table使用整理(一) http://www.jb51.net/article/115789.htm Bootstrap Table使用整理(二)  http://www.jb51.net/article/115791.htm Bootstrap Table使用整理(三)  http://www.jb51.net/article/115795.htm Bootstrap Table使用整理(四)之工具栏 http://www.jb51.net/article/1

ASP.NET MVC+EF在服务端分页使用jqGrid以及jquery Datatables的注意事项

一直想自己做个博客网站,技术路线是用ASN.NET MVC5+EF6(Code First)+ZUI+各种Jquery插件,有了这个想法之后就开始选择UI,看了好多bootstrap的模板之后,发现即使你用了bootstrap还是要自己写css样式,都是自学的,前端真的很垃圾,在网上找了很多UI,以下是各种UI的地址,需要的可以去看看: H-ui:http://www.h-ui.net/H-ui.admin.shtml ,是一个前端大牛弄得,模仿bootstrap,做适合中国网上的UI. Ama

详解基于java的Socket聊天程序——服务端(附demo)

写在前面: 昨天在博客记录自己抽空写的一个Socket聊天程序的初始设计,那是这个程序的整体设计,为了完整性,今天把服务端的设计细化记录一下,首页贴出Socket聊天程序的服务端大体设计图,如下图: 功能说明: 服务端主要有两个操作,一是阻塞接收客户端的socket并做响应处理,二是检测客户端的心跳,如果客户端一段时间内没有发送心跳则移除该客户端,由Server创建ServerSocket,然后启动两个线程池去处理这两件事(newFixedThreadPool,newScheduledThrea

Bootstrap table学习笔记(2) 前后端分页模糊查询

在使用过程中,一边看文档一边做,遇到了一些困难的地方,在此记录一下,顺便做个总结: 1.前端分页 2.后端分页 3.模糊查询 前端分页相当简单,在我添加了2w条测试数据的时候打开的很流畅,没有卡顿. $(function(){ a(); }); function a () { $('#yourtable').bootstrapTable({ url: "/user/getUserList/", method:"post", dataType: "json&

datatables 带查询条件java服务端分页处理实例

使用datatables自带后台查询 前台代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="shortcut icon" type="image/ico" href="http://www.datatables.net/favicon.ico" rel="external nof