关于 Spring For All

关于 Spring For All

Spring For All 的一切
最新动态

最新动态

Spring 5 会是咋样呢
Spring Boot

Spring Boot

快速构建并运行 Spring 应用程序
Spring Cloud

Spring Cloud

分布式系统的一套工具,可用于构建微服务
Spring Framework

Spring Framework

提供依赖注入、事务、Web应用、数据访问等模块
Spring Data

Spring Data

提供一致性数据访问模块
Spring Security

Spring Security

提供应用身份验证和授权支持
Spring Batch

Spring Batch

提供高容批处理操作模块
Spring AMQP

Spring AMQP

基于AMQP消息解决方案
Micro Service Arch.

Micro Service Arch.

微服务架构相关
开源项目及视频教程

开源项目及视频教程

做中国最好的 Spring 开源项目及视频教程
小马哥专栏

小马哥专栏

阿里技术布道者,专注 Spring Boot 及微服务

从spring转移到springboot后jpa报错Illegal mix of collations for operation 'case'

回复

jkgeekjk 发起了问题 • 1 人关注 • 0 个回复 • 33 次浏览 • 1 天前 • 来自相关话题

Spring学习(一): 使用AbstractRoutingDataSource类进行多数据源配置

zane 发表了文章 • 0 个评论 • 159 次浏览 • 2017-09-12 12:33 • 来自相关话题

多数据源在项目中很常见,举几个需求,如页面展示的数据需要从不同的数据库中去查询,修改等,例如数据库的读写分离等

配置多数据源要考虑到如下的情况
单个数据源是如下这种情况:

如果是多个数据源的话,将会变成下面这种情况



这样,每增加一个数据源就要去配置一个sessionFactory,这样比较麻烦,于是spring的
AbstractRoutingDataSource类产生了


它的实现原理是扩展Spring的`AbstractRoutingDataSource`抽象类(该类充当了`DataSource`的路由中介, 能在运行时, 根据某种key值来动态切换到真正的`DataSource`上。)
 从AbstractRoutingDataSource的源码中:$(document).ready(function() {$('pre code').each(function(i, block) { hljs.highlightBlock( block); }); });public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}

public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}

获取连接的方法中,重点是determineTargetDataSource()方法,看源码:protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

接下来,将展示一个多数据源配置demo 

1.配置一个config类,配置3个数据源(下面是JAVA配置写法)/**
* Created by Youjie on 2017/7/23.
*/
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

@Bean
@ConfigurationProperties(prefix = "datasource.db_a")
@Qualifier("dataSourceDefault")
public DataSource dataSourceDefault(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_b")
@Qualifier("dataSourceB")
public DataSource dataSourceB(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_c")
@Qualifier("dataSourceC")
public DataSource dataSourceC(){
return new DruidDataSource();
}


@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}



@Bean
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(multipleDataSource());
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("com.may.model");
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("configuration.xml"));
return sqlSessionFactoryBean.getObject();
}

@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(multipleDataSource());
}



}对应的 application.properties如下server.port=9002
server.context-path=/


#A user
datasource.db_a.driver-class-name=com.mysql.jdbc.Driver
datasource.db_a.url=jdbc:mysql://127.0.0.1:3306/test_A?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_a.username=root
datasource.db_a.password=123456

#B city
datasource.db_b.driver-class-name=com.mysql.jdbc.Driver
datasource.db_b.url=jdbc:mysql://127.0.0.1:3306/test_B?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_b.username=root
datasource.db_b.password=123456

#C role
datasource.db_c.driver-class-name=com.mysql.jdbc.Driver
datasource.db_c.url=jdbc:mysql://127.0.0.1:3306/test_C?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_c.username=root
datasource.db_c.password=123456

2、写一个ThreadLocalDynamicDataSource类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTypeManager.get();
}
}3、编写DataSourceTypeManager执行类,并利用ThreadLocal以空间换时间的方式解决线程安全问题public class DataSourceTypeManager {
private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>() {
@Override
protected DataSources initialValue() {
return DataSources.DATASOURCE_DEFAULT;
}
};

public static DataSources get() {
return dataSourceTypes.get();
}

public static void set(DataSources dataSourceType) {
dataSourceTypes.set(dataSourceType);
}

public static void reset() {
dataSourceTypes.set(DataSources.DATASOURCE_DEFAULT);
}

public static void clearDataSources () {
dataSourceTypes.remove();
}
}




上面代码利用initialValue方法,调用初始化时指定的DataSources 类型,这里DataSources 为枚举类型,里面定义三种数据源的名称,代码如下:public enum DataSources {
DATASOURCE,DATASOURCE_DEFAULT,DATASOURCE_ORACLE
}
4、multipleDataSource多数据源配置的xml配置<bean id="multipleDataSource"
class="com.hxqc.basic.dependency.datasource.ThreadLocalRountingDataSource">
<property name="defaultTargetDataSource" ref="ncDataSource"/>
<property name="targetDataSources">
<map key-type="com.hxqc.basic.dependency.datasource.DataSources">
<entry key="DATASOURCE" value-ref="erpDataSource"/>
<entry key="DATASOURCE_DEFAULT" value-ref="ncDataSource"/>
<entry key="DATASOURCE_ORACLE" value-ref="oracleDataSource"/>
</map>
</property>
</bean>



multipleDataSource多数据源配置的Java配置(上文已经贴出该配置,这里再强调一下)@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}
5.在DAOImpl中切换数据源 或者配置aop切面根据不同的包下面的数据,采用不同的数据源



1,在Dao层直接切换数据源,如切换到默认数据源DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT)
2.根据aop进行动态切换
aop xml配置<bean id="dataSourceInterceptor" class="com.hxqc.basic.dependency.interceptor.DataSourceInterceptor"/>
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:pointcut id="datasource" expression="execution(* com.hxqc.data.gather.core.erp.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_oracle" expression="execution(* com.hxqc.data.gather.core.oracle.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_default" expression="execution(* com.hxqc.data.gather.core.nc.*.mapper..*.*(..))"/>
<aop:before method="setDataSource" pointcut-ref="datasource"/>
<aop:before method="setDataSourceDefault" pointcut-ref="datasource_default"/>
<aop:before method="setDataSourceOracle" pointcut-ref="datasource_oracle"/>
</aop:aspect>
</aop:config>aop java 配置@Aspect
@Component
public class DataSourceAspect {

private Logger log = LoggerFactory.getLogger(getClass());

@Pointcut("execution(* com.may.dataA.mapper.*.*(..))")
public void pointCutA(){}

@Pointcut("execution(* com.may.dataB.mapper.*.*(..))")
public void pointCutB(){}

@Pointcut("execution(* com.may.dataC.mapper.*.*(..))")
public void pointCutC(){}
@Pointcut("execution(* com.may..*.*(..))")
public void annotation(){}



public void setDataSourceDefault(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_A);
log.info("切换数据源","切换到默认数据源A");

}
public void setDataSourceB(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_B);
log.info("切换数据源","切换到数据源B");
}

public void setDataSourceC(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_C);
log.info("切换数据源","切换到数据源C");
}

@Before("@annotation(ds)")
public void setAnnotationDataSource(JoinPoint jp,DataSource ds){
// 获取当前的指定的数据源;
String dsId = ds.value();
// 如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
/*if (!DataSourceManager.containsDataSource(dsId)) {

log.error("数据源[{}]不存在,使用默认数据源 > {}", ds.value(), jp.getSignature());

} else {

log.debug("Use DataSource : {} > {}", ds.value(), jp.getSignature());

// 找到的话,那么设置到动态数据源上下文中。
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}*/

//将string值转换为enum
DataSourceManager.set(Enum.valueOf(DataSources.class,ds.value()));

log.info("切换数据源"+ds.value(),"切换到默认数据源"+ds.value());
}



}
解释,这里定义三个切面,分别对应三个数据源,如在oracle包下的,动态切换到oracle数据源,上面配置的最后部分是用到了自定义注解DataSource注解,该注解在方法上,通过切面来进行数据源的切换,具体看源码

然后配置切面类/**
*
*
* @author maple
*
*/
public class DataSourceInterceptor {

public void setDataSourceDefault(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT);
LogU.i("切换数据源","切换到NC数据源");
System.out.println(jp.getTarget().getClass().getSimpleName());
}

public void setDataSource(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE);
LogU.i("切换数据源","切换到ERP数据源");
}

public void setDataSourceOracle(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_ORACLE);
LogU.i("切换数据源","切换到Oracle数据源");
}
}





`问题:`多数据源切换是成功了,但牵涉到事务呢?单数据源事务是ok的,但如果多数据源需要同时使用一个事务呢?这个问题有点头大,网络上有人提出用atomikos开源项目实现JTA分布式事务处理。你怎么看?

注意:

本文源码放在码云上,注意,源码将XML配置的内容都改成了JAVA配置,项目是基于Spring-boot,也提供了自定义注解来改变动态数据源!

本文源码:http://git.oschina.net/youjie1/may-dynamic-datasource
原文

  查看全部


多数据源在项目中很常见,举几个需求,如页面展示的数据需要从不同的数据库中去查询,修改等,例如数据库的读写分离等


配置多数据源要考虑到如下的情况
单个数据源是如下这种情况:

如果是多个数据源的话,将会变成下面这种情况



这样,每增加一个数据源就要去配置一个sessionFactory,这样比较麻烦,于是spring的
AbstractRoutingDataSource类产生了


它的实现原理是扩展Spring的`AbstractRoutingDataSource`抽象类(该类充当了`DataSource`的路由中介, 能在运行时, 根据某种key值来动态切换到真正的`DataSource`上。)
 从AbstractRoutingDataSource的源码中:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean


我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:
public Connection getConnection() throws SQLException {  
return determineTargetDataSource().getConnection();
}

public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}


获取连接的方法中,重点是determineTargetDataSource()方法,看源码:
protected DataSource determineTargetDataSource() {  
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

接下来,将展示一个多数据源配置demo 

1.配置一个config类,配置3个数据源(下面是JAVA配置写法)
/**
* Created by Youjie on 2017/7/23.
*/
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

@Bean
@ConfigurationProperties(prefix = "datasource.db_a")
@Qualifier("dataSourceDefault")
public DataSource dataSourceDefault(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_b")
@Qualifier("dataSourceB")
public DataSource dataSourceB(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_c")
@Qualifier("dataSourceC")
public DataSource dataSourceC(){
return new DruidDataSource();
}


@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}



@Bean
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(multipleDataSource());
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("com.may.model");
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("configuration.xml"));
return sqlSessionFactoryBean.getObject();
}

@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(multipleDataSource());
}



}
对应的 application.properties如下
server.port=9002
server.context-path=/


#A user
datasource.db_a.driver-class-name=com.mysql.jdbc.Driver
datasource.db_a.url=jdbc:mysql://127.0.0.1:3306/test_A?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_a.username=root
datasource.db_a.password=123456

#B city
datasource.db_b.driver-class-name=com.mysql.jdbc.Driver
datasource.db_b.url=jdbc:mysql://127.0.0.1:3306/test_B?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_b.username=root
datasource.db_b.password=123456

#C role
datasource.db_c.driver-class-name=com.mysql.jdbc.Driver
datasource.db_c.url=jdbc:mysql://127.0.0.1:3306/test_C?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_c.username=root
datasource.db_c.password=123456


2、写一个ThreadLocalDynamicDataSource类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法
public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTypeManager.get();
}
}
3、编写DataSourceTypeManager执行类,并利用ThreadLocal以空间换时间的方式解决线程安全问题
public class DataSourceTypeManager {
private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>() {
@Override
protected DataSources initialValue() {
return DataSources.DATASOURCE_DEFAULT;
}
};

public static DataSources get() {
return dataSourceTypes.get();
}

public static void set(DataSources dataSourceType) {
dataSourceTypes.set(dataSourceType);
}

public static void reset() {
dataSourceTypes.set(DataSources.DATASOURCE_DEFAULT);
}

public static void clearDataSources () {
dataSourceTypes.remove();
}
}




上面代码利用initialValue方法,调用初始化时指定的DataSources 类型,这里DataSources 为枚举类型,里面定义三种数据源的名称,代码如下:
public enum DataSources {
DATASOURCE,DATASOURCE_DEFAULT,DATASOURCE_ORACLE
}

4、multipleDataSource多数据源配置的xml配置
<bean id="multipleDataSource"
class="com.hxqc.basic.dependency.datasource.ThreadLocalRountingDataSource">
<property name="defaultTargetDataSource" ref="ncDataSource"/>
<property name="targetDataSources">
<map key-type="com.hxqc.basic.dependency.datasource.DataSources">
<entry key="DATASOURCE" value-ref="erpDataSource"/>
<entry key="DATASOURCE_DEFAULT" value-ref="ncDataSource"/>
<entry key="DATASOURCE_ORACLE" value-ref="oracleDataSource"/>
</map>
</property>
</bean>



multipleDataSource多数据源配置的Java配置(上文已经贴出该配置,这里再强调一下)
@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}

5.在DAOImpl中切换数据源 或者配置aop切面根据不同的包下面的数据,采用不同的数据源



1,在Dao层直接切换数据源,如切换到默认数据源
DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT)

2.根据aop进行动态切换
aop xml配置
<bean id="dataSourceInterceptor" class="com.hxqc.basic.dependency.interceptor.DataSourceInterceptor"/>
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:pointcut id="datasource" expression="execution(* com.hxqc.data.gather.core.erp.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_oracle" expression="execution(* com.hxqc.data.gather.core.oracle.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_default" expression="execution(* com.hxqc.data.gather.core.nc.*.mapper..*.*(..))"/>
<aop:before method="setDataSource" pointcut-ref="datasource"/>
<aop:before method="setDataSourceDefault" pointcut-ref="datasource_default"/>
<aop:before method="setDataSourceOracle" pointcut-ref="datasource_oracle"/>
</aop:aspect>
</aop:config>
aop java 配置
@Aspect
@Component
public class DataSourceAspect {

private Logger log = LoggerFactory.getLogger(getClass());

@Pointcut("execution(* com.may.dataA.mapper.*.*(..))")
public void pointCutA(){}

@Pointcut("execution(* com.may.dataB.mapper.*.*(..))")
public void pointCutB(){}

@Pointcut("execution(* com.may.dataC.mapper.*.*(..))")
public void pointCutC(){}
@Pointcut("execution(* com.may..*.*(..))")
public void annotation(){}



public void setDataSourceDefault(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_A);
log.info("切换数据源","切换到默认数据源A");

}
public void setDataSourceB(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_B);
log.info("切换数据源","切换到数据源B");
}

public void setDataSourceC(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_C);
log.info("切换数据源","切换到数据源C");
}

@Before("@annotation(ds)")
public void setAnnotationDataSource(JoinPoint jp,DataSource ds){
// 获取当前的指定的数据源;
String dsId = ds.value();
// 如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
/*if (!DataSourceManager.containsDataSource(dsId)) {

log.error("数据源[{}]不存在,使用默认数据源 > {}", ds.value(), jp.getSignature());

} else {

log.debug("Use DataSource : {} > {}", ds.value(), jp.getSignature());

// 找到的话,那么设置到动态数据源上下文中。
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}*/

//将string值转换为enum
DataSourceManager.set(Enum.valueOf(DataSources.class,ds.value()));

log.info("切换数据源"+ds.value(),"切换到默认数据源"+ds.value());
}



}

解释,这里定义三个切面,分别对应三个数据源,如在oracle包下的,动态切换到oracle数据源,上面配置的最后部分是用到了自定义注解DataSource注解,该注解在方法上,通过切面来进行数据源的切换,具体看源码

然后配置切面类
/**
*
*
* @author maple
*
*/
public class DataSourceInterceptor {

public void setDataSourceDefault(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT);
LogU.i("切换数据源","切换到NC数据源");
System.out.println(jp.getTarget().getClass().getSimpleName());
}

public void setDataSource(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE);
LogU.i("切换数据源","切换到ERP数据源");
}

public void setDataSourceOracle(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_ORACLE);
LogU.i("切换数据源","切换到Oracle数据源");
}
}





`问题:`多数据源切换是成功了,但牵涉到事务呢?单数据源事务是ok的,但如果多数据源需要同时使用一个事务呢?这个问题有点头大,网络上有人提出用atomikos开源项目实现JTA分布式事务处理。你怎么看?

注意:

本文源码放在码云上,注意,源码将XML配置的内容都改成了JAVA配置,项目是基于Spring-boot,也提供了自定义注解来改变动态数据源!

本文源码:http://git.oschina.net/youjie1/may-dynamic-datasource
原文

 

Spring Data Redis - Could not safely identify store assignment for repository

回复

wayne 发起了问题 • 1 人关注 • 0 个回复 • 196 次浏览 • 2017-09-04 13:45 • 来自相关话题

spring boot+spring data jpa+多数据源+Jsr310JpaConverters

回复

passion 回复了问题 • 1 人关注 • 2 个回复 • 500 次浏览 • 2017-07-27 09:27 • 来自相关话题

Spring Data ElasticSearch 要视频吗?

泥瓦匠BYSocket 回复了问题 • 14 人关注 • 14 个回复 • 1016 次浏览 • 2017-07-21 00:31 • 来自相关话题

Spring Data JPA系列:数据查询(Specification)(二)

wayne 发表了文章 • 0 个评论 • 497 次浏览 • 2017-07-18 16:36 • 来自相关话题

写了一系列入门文章之后,博客也有了一些访问量,按照计划,对数据查询进行深入一些的探究,包括
- inner join查询
- 连接对象的属性值查询
- in条件查询
- left join查询
还是入门级的示例,更深入的用法需要在实际场景中深化。

1、更改Customer类

增加@OneToMany注解的订单对象
需要注意的是,这次增加了Lombok依赖,一个简化对象类定义的插件,详见:https://projectlombok.org/
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.NamedQuery;
import javax.persistence.*;
import java.util.List;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@NamedQuery(name="Customer.findByFirstName",query = "select c from Customer c where c.firstName = ?1")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;

//一对多,一个客户对应多个订单,关联的字段是订单里的cId字段
@OneToMany
@JoinColumn(name = "cId")
private List<MyOrder> myOrders;
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return String.format(
"Customer[id=%d, firstName='%s', lastName='%s']",
id, firstName, lastName);
}
}
2、增加MyOrder类

我的订单对象
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.math.BigDecimal;
/**
* Created by Administrator on 2017/7/17 0017.
*/
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyOrder {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String code;
private Long cId;
private BigDecimal total;
//实体映射重复列必须设置:insertable = false,updatable = false
@OneToOne
@JoinColumn(name = "cId",insertable = false,updatable = false)
private Customer customer;
@Override
public String toString() {
return "MyOrder{" +
"id=" + id +
", code='" + code + '\'' +
", cId=" + cId +
", total=" + total +
", customer=" + customer +
'}';
}
}3、新增MyOrderRepository类

这里主要是继承JpaSpecificationExecutor接口,进行Specification查询
import com.example.demo.dto.MyOrder;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
/**
* Created by Administrator on 2017/7/17 0017.
*/
public interface MyOrderRepository extends JpaSpecificationExecutor<MyOrder>,CrudRepository<MyOrder,Long> {
}4、新增ShoppingController类
import com.example.demo.dto.Customer;
import com.example.demo.dto.MyOrder;
import com.example.demo.repositories.MyOrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.persistence.criteria.*;

@Controller
@RequestMapping("/shop")
public class ShoppingController {
@Autowired
private MyOrderRepository myOrderRepository;

/**
* 内连接查询
*/
@RequestMapping("/q1")
public void specification1() {
//根据查询结果,声明返回值对象,这里要查询用户的订单列表,所以声明返回对象为MyOrder
Specification<MyOrder> spec = new Specification<MyOrder>() {
//Root<X> 根查询,默认与声明相同
@Override
public Predicate toPredicate(Root<MyOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//声明并创建MyOrder的CriteriaQuery对象
CriteriaQuery<MyOrder> q1 = cb.createQuery(MyOrder.class);
//连接的时候,要以声明的根查询对象(这里是root,也可以自己创建)进行join
//Join<Z,X>是Join生成的对象,这里的Z是被连接的对象,X是目标对象,
// 连接的属性字段是被连接的对象在目标对象的属性,这里是我们在MyOrder内声明的customer
//join的第二个参数是可选的,默认是JoinType.INNER(内连接 inner join),也可以是JoinType.LEFT(左外连接 left join)
Join<Customer, MyOrder> myOrderJoin = root.join("customer", JoinType.INNER);
//用CriteriaQuery对象拼接查询条件,这里只增加了一个查询条件,cId=1
q1.select(myOrderJoin).where(cb.equal(root.get("cId"), 1));
//通过getRestriction获得Predicate对象
Predicate p1 = q1.getRestriction();
//返回对象
return p1;
}
};
resultPrint(spec);
}


/**
* 增加查询条件,关联的对象Customer的对象值
*/
@RequestMapping("/q2")
public void specification2() {
Specification<MyOrder> spec = new Specification<MyOrder>() {
@Override
public Predicate toPredicate(Root<MyOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<MyOrder> q1 = cb.createQuery(MyOrder.class);
Join<Customer, MyOrder> myOrderJoin = root.join("customer");
q1.select(myOrderJoin)
.where(
cb.equal(root.get("cId"), 1),//cId=1
cb.equal(root.get("customer").get("firstName"), "Jack")//对象customer的firstName=Jack
);
Predicate p1 = q1.getRestriction();
return p1;
}
};
resultPrint(spec);
}

/**
* in的条件查询
* 需要将对应的结果集以root.get("attributeName").in(Object.. values)的方式传入
* values支持多个参数,支持对象(Object),表达式Expression<?>,集合Collection以及Expression<Collection<?>>
*/
@RequestMapping("/q3")
public void specification3() {
Specification<MyOrder> spec = new Specification<MyOrder>() {
@Override
public Predicate toPredicate(Root<MyOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<MyOrder> q1 = cb.createQuery(MyOrder.class);
Join<Customer, MyOrder> myOrderJoin = root.join("customer");
q1.select(myOrderJoin)
.where(
cb.equal(root.get("cId"), 1)
, root.get("id").in(1, 2, 4)
);

Predicate p1 = q1.getRestriction();
return p1;
}
};
resultPrint(spec);
}


/**
* 左外链接查询,对比inner join,
* 这里只是改了一个参数,将JoinType.INNER改成JoinType.LEFT
* <p>
* 注意,当前示例不支持JoinType.RIGHT,用的比较少,没有探究
*/
@RequestMapping("/q4")
public void specification4() {
Specification<MyOrder> spec = new Specification<MyOrder>() {
@Override
public Predicate toPredicate(Root<MyOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<MyOrder> q1 = cb.createQuery(MyOrder.class);
Join<Customer, MyOrder> myOrderJoin = root.join("customer", JoinType.LEFT);
q1.select(myOrderJoin).where(cb.equal(root.get("cId"), 1));
Predicate p1 = q1.getRestriction();
return p1;
}
};
resultPrint(spec);
}


private void resultPrint(Specification<MyOrder> spec) {
//分页查询
Pageable pageable = new PageRequest(0, 10, Sort.Direction.DESC, "id");
//查询的分页结果
Page<MyOrder> page = myOrderRepository.findAll(spec, pageable);
System.out.println(page);
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
for (MyOrder c : page.getContent()) {
System.out.println(c.toString());
}
}

}
5、测试
1)、内连接查询及结果:
URL:http://localhost:8080/shop/q1 
2)、关联对象条件查询及结果:
URL:http://localhost:8080/shop/q2
3)、in条件查询测试及结果:
URL:http://localhost:8080/shop/q3
4)、左外连接测试及结果:
URL:http://localhost:8080/shop/q4
 
参考:
官方文档,https://docs.spring.io/spring- ... /html 
DEMO,https://github.com/icnws/spring-data-jpa-demo
个人博客,http://www.icnws.com
本文链接:http://www.icnws.com/?p=578

版权归作者所有,转载请注明! 查看全部
写了一系列入门文章之后,博客也有了一些访问量,按照计划,对数据查询进行深入一些的探究,包括
- inner join查询
- 连接对象的属性值查询
- in条件查询
- left join查询
还是入门级的示例,更深入的用法需要在实际场景中深化。

1、更改Customer类

增加@OneToMany注解的订单对象
需要注意的是,这次增加了Lombok依赖,一个简化对象类定义的插件,详见:https://projectlombok.org/
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.NamedQuery;
import javax.persistence.*;
import java.util.List;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@NamedQuery(name="Customer.findByFirstName",query = "select c from Customer c where c.firstName = ?1")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;

//一对多,一个客户对应多个订单,关联的字段是订单里的cId字段
@OneToMany
@JoinColumn(name = "cId")
private List<MyOrder> myOrders;
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return String.format(
"Customer[id=%d, firstName='%s', lastName='%s']",
id, firstName, lastName);
}
}

2、增加MyOrder类

我的订单对象
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.math.BigDecimal;
/**
* Created by Administrator on 2017/7/17 0017.
*/
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyOrder {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String code;
private Long cId;
private BigDecimal total;
//实体映射重复列必须设置:insertable = false,updatable = false
@OneToOne
@JoinColumn(name = "cId",insertable = false,updatable = false)
private Customer customer;
@Override
public String toString() {
return "MyOrder{" +
"id=" + id +
", code='" + code + '\'' +
", cId=" + cId +
", total=" + total +
", customer=" + customer +
'}';
}
}
3、新增MyOrderRepository类

这里主要是继承JpaSpecificationExecutor接口,进行Specification查询
import com.example.demo.dto.MyOrder;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
/**
* Created by Administrator on 2017/7/17 0017.
*/
public interface MyOrderRepository extends JpaSpecificationExecutor<MyOrder>,CrudRepository<MyOrder,Long> {
}
4、新增ShoppingController类
import com.example.demo.dto.Customer;
import com.example.demo.dto.MyOrder;
import com.example.demo.repositories.MyOrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.persistence.criteria.*;

@Controller
@RequestMapping("/shop")
public class ShoppingController {
@Autowired
private MyOrderRepository myOrderRepository;

/**
* 内连接查询
*/
@RequestMapping("/q1")
public void specification1() {
//根据查询结果,声明返回值对象,这里要查询用户的订单列表,所以声明返回对象为MyOrder
Specification<MyOrder> spec = new Specification<MyOrder>() {
//Root<X> 根查询,默认与声明相同
@Override
public Predicate toPredicate(Root<MyOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//声明并创建MyOrder的CriteriaQuery对象
CriteriaQuery<MyOrder> q1 = cb.createQuery(MyOrder.class);
//连接的时候,要以声明的根查询对象(这里是root,也可以自己创建)进行join
//Join<Z,X>是Join生成的对象,这里的Z是被连接的对象,X是目标对象,
// 连接的属性字段是被连接的对象在目标对象的属性,这里是我们在MyOrder内声明的customer
//join的第二个参数是可选的,默认是JoinType.INNER(内连接 inner join),也可以是JoinType.LEFT(左外连接 left join)
Join<Customer, MyOrder> myOrderJoin = root.join("customer", JoinType.INNER);
//用CriteriaQuery对象拼接查询条件,这里只增加了一个查询条件,cId=1
q1.select(myOrderJoin).where(cb.equal(root.get("cId"), 1));
//通过getRestriction获得Predicate对象
Predicate p1 = q1.getRestriction();
//返回对象
return p1;
}
};
resultPrint(spec);
}


/**
* 增加查询条件,关联的对象Customer的对象值
*/
@RequestMapping("/q2")
public void specification2() {
Specification<MyOrder> spec = new Specification<MyOrder>() {
@Override
public Predicate toPredicate(Root<MyOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<MyOrder> q1 = cb.createQuery(MyOrder.class);
Join<Customer, MyOrder> myOrderJoin = root.join("customer");
q1.select(myOrderJoin)
.where(
cb.equal(root.get("cId"), 1),//cId=1
cb.equal(root.get("customer").get("firstName"), "Jack")//对象customer的firstName=Jack
);
Predicate p1 = q1.getRestriction();
return p1;
}
};
resultPrint(spec);
}

/**
* in的条件查询
* 需要将对应的结果集以root.get("attributeName").in(Object.. values)的方式传入
* values支持多个参数,支持对象(Object),表达式Expression<?>,集合Collection以及Expression<Collection<?>>
*/
@RequestMapping("/q3")
public void specification3() {
Specification<MyOrder> spec = new Specification<MyOrder>() {
@Override
public Predicate toPredicate(Root<MyOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<MyOrder> q1 = cb.createQuery(MyOrder.class);
Join<Customer, MyOrder> myOrderJoin = root.join("customer");
q1.select(myOrderJoin)
.where(
cb.equal(root.get("cId"), 1)
, root.get("id").in(1, 2, 4)
);

Predicate p1 = q1.getRestriction();
return p1;
}
};
resultPrint(spec);
}


/**
* 左外链接查询,对比inner join,
* 这里只是改了一个参数,将JoinType.INNER改成JoinType.LEFT
* <p>
* 注意,当前示例不支持JoinType.RIGHT,用的比较少,没有探究
*/
@RequestMapping("/q4")
public void specification4() {
Specification<MyOrder> spec = new Specification<MyOrder>() {
@Override
public Predicate toPredicate(Root<MyOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<MyOrder> q1 = cb.createQuery(MyOrder.class);
Join<Customer, MyOrder> myOrderJoin = root.join("customer", JoinType.LEFT);
q1.select(myOrderJoin).where(cb.equal(root.get("cId"), 1));
Predicate p1 = q1.getRestriction();
return p1;
}
};
resultPrint(spec);
}


private void resultPrint(Specification<MyOrder> spec) {
//分页查询
Pageable pageable = new PageRequest(0, 10, Sort.Direction.DESC, "id");
//查询的分页结果
Page<MyOrder> page = myOrderRepository.findAll(spec, pageable);
System.out.println(page);
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
for (MyOrder c : page.getContent()) {
System.out.println(c.toString());
}
}

}
5、测试
1)、内连接查询及结果:
URL:http://localhost:8080/shop/q1 
2)、关联对象条件查询及结果:
URL:http://localhost:8080/shop/q2
3)、in条件查询测试及结果:
URL:http://localhost:8080/shop/q3
4)、左外连接测试及结果:
URL:http://localhost:8080/shop/q4
 
参考:
官方文档,https://docs.spring.io/spring- ... /html 
DEMO,https://github.com/icnws/spring-data-jpa-demo
个人博客,http://www.icnws.com
本文链接:http://www.icnws.com/?p=578


版权归作者所有,转载请注明!


使用springdata操作es出错org.elasticsearch.client.transport.NoNodeAvailableException

strongant 发表了文章 • 3 个评论 • 252 次浏览 • 2017-07-16 13:42 • 来自相关话题

  之前的项目中使用的是http-client操作es,比较轻量级。目前为了学习下springdata,使用Java API 的方式来操作ES,但是在引入springdata的es模块依赖后,操作es并未成功,出现以下错误:org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{127.0.0.1}{127.0.0.1:9300}]
注意:如果你下载elasticsearch的压缩包安装的话,可能不会出现该问题!因为es默认的配置文件cluster.name是elasticsearch。但是奇葩的是使用brew安装es之后,默认的elasticsearch.yml的配置项成了这样:cluster.name: elasticsearch_bwh,就是这个原因导致了这个问题的产生,项目启动后控制台一直抛这个错:transport#-1}{127.0.0.1}{127.0.0.1:9300} not part of the cluster Cluster [Assassin], ignoring...当执行添加操作时,提示:org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{127.0.0.1}{127.0.0.1:9300}]最后需要注意的是,如果你修改了es默认的集群名称,则需要在src/main/resources/application.properties配置文件中进行指定:

spring.data.elasticsearch.clusterName=elasticsearch_bwh
通过源码org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchProperties可以看到,默认的
clusterName为elasticsearch。
 
希望可以帮助遇到此类问题的同学。 查看全部
  之前的项目中使用的是http-client操作es,比较轻量级。目前为了学习下springdata,使用Java API 的方式来操作ES,但是在引入springdata的es模块依赖后,操作es并未成功,出现以下错误:
org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{127.0.0.1}{127.0.0.1:9300}]

注意:如果你下载elasticsearch的压缩包安装的话,可能不会出现该问题!因为es默认的配置文件cluster.name是elasticsearch。但是奇葩的是使用brew安装es之后,默认的elasticsearch.yml的配置项成了这样:cluster.name: elasticsearch_bwh,就是这个原因导致了这个问题的产生,项目启动后控制台一直抛这个错:
transport#-1}{127.0.0.1}{127.0.0.1:9300} not part of the cluster Cluster [Assassin], ignoring...
当执行添加操作时,提示:
org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{127.0.0.1}{127.0.0.1:9300}]
最后需要注意的是,如果你修改了es默认的集群名称,则需要在src/main/resources/application.properties配置文件中进行指定:

spring.data.elasticsearch.clusterName=elasticsearch_bwh
通过源码org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchProperties可以看到,默认的
clusterName为elasticsearch。
 
希望可以帮助遇到此类问题的同学。

Spring Data JPA系列:数据查询(Specification)(一)

wayne 发表了文章 • 1 个评论 • 528 次浏览 • 2017-07-12 22:45 • 来自相关话题

在JPA中,如果用@Query查询有诸多不便,因此JPA提供了基于Criteria API的查询,比@Query查询更灵活方便,这篇文章就简单说说Specification的简单使用。
这篇文章是参考:Useing JPA Criteria API ,相关代码如下所示:

1、定义一个继承JpaSpecificationExecutor的接口:import com.example.demo.dto.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* Created by Administrator on 2017/7/12 0012.
*/
public interface CustomerSpecificationRepository extends JpaRepository<Customer,Long>,
JpaSpecificationExecutor<Customer> {
}这里只是继承接口声明的方法,比如T findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);
long count(Specification<T> spec);包含了常用的查询单个对象,查询数据集合,查询分页数据集合,查询带排序参数的数据集合,查询数据的大小,这些都是常用的数据结果集,因此可以不用做其他定义即可直接使用。

2、创建SpecificationFactory工具类
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.Path;
/**
* Created by Administrator on 2017/7/13 0013.
*/
public final class SpecificationFactory {
public static Specification containsLike(String attribute, String value) {
return (root, query, cb) -> cb.like(root.get(attribute), "%" + value + "%");
}
public static Specification isBetween(String attribute, int min, int max) {
return (root, query, cb) -> cb.between(root.get(attribute), min, max);
}
public static Specification isBetween(String attribute, double min, double max) {
return (root, query, cb) -> cb.between(root.get(attribute), min, max);
}
public static <T extends Enum<T>> Specification enumMatcher(String attribute, T queriedValue) {
return (root, query, cb) -> {
Path<T> actualValue = root.get(attribute);
if (queriedValue == null) {
return null;
}
return cb.equal(actualValue, queriedValue);
};
}
}这个工具类定义了一些基本的查询,包括模糊匹配(containsLike),数值区间(isBetween)以及枚举类参数匹配(enumMatcher),这些也是在查询中常用的方法,在查询的时候,通过Specifications进行调用组织参数,即可获得接口Specification的实例,然后用于查询。

3、在CustomerController中添加测试方法
初始化
@Autowired
private CustomerSpecificationRepository csr;
单一条件查询
@RequestMapping("/spec")
public void specificationQuery(){
Specification<Customer> spec = SpecificationFactory.containsLike("lastName","bau");
Pageable pageable = new PageRequest(0,5, Sort.Direction.DESC,"id");
Page<Customer> page = csr.findAll(spec,pageable);
System.out.println(page);
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
for (Customer c:page.getContent()){
System.out.println(c.toString());
}
}
复合条件查询
@RequestMapping("/spec2")
public void specificationQuery2(){
Specification<Customer> spec2 = Specifications
.where(SpecificationFactory.containsLike("firstName","bau"))
.or(SpecificationFactory.containsLike("lastName","bau"));
Pageable pageable = new PageRequest(0,5, Sort.Direction.DESC,"id");
Page<Customer> page = csr.findAll(spec2,pageable);
System.out.println(page);
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
for (Customer c:page.getContent()){
System.out.println(c.toString());
}
}至此,使用Specification通过Criteria API进行查询,获得查询的结果集。相较于@Query方式的查询定义,更人性化和方便操作,后面在对Criteria API的使用进一步深入,会继续给出相关实践,敬请期待。

参考:
官方文档,https://docs.spring.io/spring- ... /html 
DEMO,https://github.com/icnws/spring-data-jpa-demo
个人博客,http://www.icnws.com 查看全部
在JPA中,如果用@Query查询有诸多不便,因此JPA提供了基于Criteria API的查询,比@Query查询更灵活方便,这篇文章就简单说说Specification的简单使用。
这篇文章是参考:Useing JPA Criteria API ,相关代码如下所示:

1、定义一个继承JpaSpecificationExecutor的接口:
import com.example.demo.dto.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* Created by Administrator on 2017/7/12 0012.
*/
public interface CustomerSpecificationRepository extends JpaRepository<Customer,Long>,
JpaSpecificationExecutor<Customer> {
}
这里只是继承接口声明的方法,比如
T findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);
long count(Specification<T> spec);
包含了常用的查询单个对象,查询数据集合,查询分页数据集合,查询带排序参数的数据集合,查询数据的大小,这些都是常用的数据结果集,因此可以不用做其他定义即可直接使用。

2、创建SpecificationFactory工具类
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.Path;
/**
* Created by Administrator on 2017/7/13 0013.
*/
public final class SpecificationFactory {
public static Specification containsLike(String attribute, String value) {
return (root, query, cb) -> cb.like(root.get(attribute), "%" + value + "%");
}
public static Specification isBetween(String attribute, int min, int max) {
return (root, query, cb) -> cb.between(root.get(attribute), min, max);
}
public static Specification isBetween(String attribute, double min, double max) {
return (root, query, cb) -> cb.between(root.get(attribute), min, max);
}
public static <T extends Enum<T>> Specification enumMatcher(String attribute, T queriedValue) {
return (root, query, cb) -> {
Path<T> actualValue = root.get(attribute);
if (queriedValue == null) {
return null;
}
return cb.equal(actualValue, queriedValue);
};
}
}
这个工具类定义了一些基本的查询,包括模糊匹配(containsLike),数值区间(isBetween)以及枚举类参数匹配(enumMatcher),这些也是在查询中常用的方法,在查询的时候,通过Specifications进行调用组织参数,即可获得接口Specification的实例,然后用于查询。

3、在CustomerController中添加测试方法
  • 初始化

@Autowired
private CustomerSpecificationRepository csr;

  • 单一条件查询

  @RequestMapping("/spec")
public void specificationQuery(){
Specification<Customer> spec = SpecificationFactory.containsLike("lastName","bau");
Pageable pageable = new PageRequest(0,5, Sort.Direction.DESC,"id");
Page<Customer> page = csr.findAll(spec,pageable);
System.out.println(page);
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
for (Customer c:page.getContent()){
System.out.println(c.toString());
}
}

  • 复合条件查询

  @RequestMapping("/spec2")
public void specificationQuery2(){
Specification<Customer> spec2 = Specifications
.where(SpecificationFactory.containsLike("firstName","bau"))
.or(SpecificationFactory.containsLike("lastName","bau"));
Pageable pageable = new PageRequest(0,5, Sort.Direction.DESC,"id");
Page<Customer> page = csr.findAll(spec2,pageable);
System.out.println(page);
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
for (Customer c:page.getContent()){
System.out.println(c.toString());
}
}
至此,使用Specification通过Criteria API进行查询,获得查询的结果集。相较于@Query方式的查询定义,更人性化和方便操作,后面在对Criteria API的使用进一步深入,会继续给出相关实践,敬请期待。

参考:
官方文档,https://docs.spring.io/spring- ... /html 
DEMO,https://github.com/icnws/spring-data-jpa-demo
个人博客,http://www.icnws.com

spring data jpa主键问题

魔王是小白 回复了问题 • 3 人关注 • 3 个回复 • 220 次浏览 • 2017-07-12 16:26 • 来自相关话题

springboot jpa 分页查询页数错误

回复

xiaoxiao 回复了问题 • 2 人关注 • 2 个回复 • 362 次浏览 • 2017-07-11 11:46 • 来自相关话题