├── README.md ├── src ├── main │ ├── java │ │ └── com │ │ │ └── rick │ │ │ ├── annotation │ │ │ └── DataSourceAnnotation.java │ │ │ ├── SpringBootDruidMultiDbApplication.java │ │ │ ├── datasource │ │ │ ├── DynamicDataSource.java │ │ │ └── DynamicDataSourceContextHolder.java │ │ │ ├── service │ │ │ ├── StudentService.java │ │ │ └── impl │ │ │ │ └── StudentServiceImpl.java │ │ │ ├── mappers │ │ │ ├── StudentMapper.java │ │ │ └── employeeMapper.xml │ │ │ ├── entities │ │ │ └── Student.java │ │ │ ├── aspect │ │ │ └── DynamicDataSourceAspect.java │ │ │ ├── config │ │ │ ├── SessionFactoryConfig.java │ │ │ └── DruidConfig.java │ │ │ ├── common │ │ │ └── AppConstants.java │ │ │ └── controller │ │ │ └── ApiController.java │ └── resources │ │ ├── log4j2.xml │ │ └── application.properties └── test │ └── java │ └── com │ └── rick │ └── SpringBootDruidMultiDbApplicationTests.java ├── sonar-project.properties └── pom.xml /README.md: -------------------------------------------------------------------------------- 1 | # SpringBootDruidMultiDB 2 | 本项目研究SpringBoot+Mybatis+Druid使用多数据源的集成 3 | -------------------------------------------------------------------------------- /src/main/java/com/rick/annotation/DataSourceAnnotation.java: -------------------------------------------------------------------------------- 1 | package com.rick.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Retention(RetentionPolicy.RUNTIME) 6 | @Target({ElementType.METHOD, ElementType.TYPE}) 7 | @Documented 8 | public @interface DataSourceAnnotation { 9 | } 10 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=SpringBootDruidMultiDB 2 | sonar.projectName=SpringBootDruidMultiDB 3 | sonar.projectVersion=0.1.0 4 | sonar.sources=src 5 | sonar.language=java 6 | sonar.sourceEncoding=UTF-8 7 | sonar.java.binaries=target/classes,target/test-classes 8 | sonar.java.source=1.8 9 | sonar.java.target=1.8 -------------------------------------------------------------------------------- /src/main/java/com/rick/SpringBootDruidMultiDbApplication.java: -------------------------------------------------------------------------------- 1 | package com.rick; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootDruidMultiDbApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootDruidMultiDbApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/rick/datasource/DynamicDataSource.java: -------------------------------------------------------------------------------- 1 | package com.rick.datasource; 2 | 3 | import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 4 | 5 | public class DynamicDataSource extends AbstractRoutingDataSource { 6 | 7 | /** 8 | * 获得数据源 9 | */ 10 | @Override 11 | protected Object determineCurrentLookupKey() { 12 | return DynamicDataSourceContextHolder.getDateSoureType(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/rick/SpringBootDruidMultiDbApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.rick; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringBootDruidMultiDbApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/rick/service/StudentService.java: -------------------------------------------------------------------------------- 1 | package com.rick.service; 2 | 3 | import com.rick.entities.Student; 4 | 5 | 6 | public interface StudentService { 7 | 8 | /** 9 | * 插入学生信息 10 | * @param student 学生信息 11 | * @param dbType 数据源Id 12 | * @return 插入成功后的学生id 13 | */ 14 | int insertStudent(Student student, String dsId); 15 | 16 | 17 | 18 | /** 19 | * 根据学生id查找学生信息 20 | * @param id 学生id 21 | * @param dsId 数据源Id 22 | * @return 如果学生存在,返回学生对象,反之返回null 23 | */ 24 | Student findStudentById(int id, String dsId); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/rick/mappers/StudentMapper.java: -------------------------------------------------------------------------------- 1 | package com.rick.mappers; 2 | 3 | import com.rick.entities.Student; 4 | import org.apache.ibatis.annotations.Mapper; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | @Mapper 10 | public interface StudentMapper { 11 | 12 | Student findStudentById(int id); 13 | 14 | int insertStudent(Student student); 15 | 16 | void insertStudents(List students); 17 | 18 | int updateStudent(Student student); 19 | 20 | int deleteStudentById(int id); 21 | 22 | List findStudentByCondition(Map condition); 23 | 24 | int deleteStudentByCondition(Map condition); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/rick/service/impl/StudentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.rick.service.impl; 2 | 3 | import com.rick.annotation.DataSourceAnnotation; 4 | import com.rick.entities.Student; 5 | import com.rick.mappers.StudentMapper; 6 | import com.rick.service.StudentService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | 12 | @Service("studentService") 13 | @Transactional 14 | public class StudentServiceImpl implements StudentService { 15 | 16 | @Autowired 17 | private StudentMapper studentMapper; 18 | 19 | @Override 20 | /** 21 | * 插入学生信息 22 | * @param student 学生信息 23 | * @param dbType 数据源Id 24 | * @return 插入成功后的学生id 25 | */ 26 | public int insertStudent(Student student, String dsId) { 27 | studentMapper.insertStudent(student); 28 | return student.getId(); 29 | } 30 | 31 | @Override 32 | /** 33 | * 根据学生id查找学生信息 34 | * @param id 学生id 35 | * @param dsId 数据源Id 36 | * @return 如果学生存在,返回学生对象,反之返回null 37 | */ 38 | @DataSourceAnnotation 39 | public Student findStudentById(int id, String dsId) { 40 | return studentMapper.findStudentById(id); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/rick/entities/Student.java: -------------------------------------------------------------------------------- 1 | package com.rick.entities; 2 | 3 | import java.io.Serializable; 4 | import java.sql.Timestamp; 5 | 6 | public class Student implements Serializable { 7 | 8 | /** 9 | * 10 | */ 11 | private static final long serialVersionUID = -179255268078151438L; 12 | 13 | private int id; 14 | 15 | private String name; 16 | private String className; 17 | private Timestamp createDate; 18 | private Timestamp updateDate; 19 | 20 | 21 | public int getId() { 22 | return id; 23 | } 24 | 25 | public void setId(int id) { 26 | this.id = id; 27 | } 28 | 29 | public String getName() { 30 | return name; 31 | } 32 | 33 | public void setName(String name) { 34 | this.name = name; 35 | } 36 | 37 | public String getClassName() { 38 | return className; 39 | } 40 | 41 | public void setClassName(String className) { 42 | this.className = className; 43 | } 44 | 45 | public Timestamp getCreateDate() { 46 | return createDate; 47 | } 48 | 49 | public void setCreateDate(Timestamp createDate) { 50 | this.createDate = createDate; 51 | } 52 | 53 | public Timestamp getUpdateDate() { 54 | return updateDate; 55 | } 56 | 57 | public void setUpdateDate(Timestamp updateDate) { 58 | this.updateDate = updateDate; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/rick/datasource/DynamicDataSourceContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.rick.datasource; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class DynamicDataSourceContextHolder { 7 | 8 | /* 9 | * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, 10 | * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 11 | */ 12 | private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal(); 13 | 14 | /* 15 | * 管理所有的数据源id,用于数据源的判断 16 | */ 17 | public static List datasourceId = new ArrayList(); 18 | 19 | /** 20 | * @Title: setDateSoureType 21 | * @Description: 设置数据源的变量 22 | * @param dateSoureType 23 | * @return void 24 | * @throws 25 | */ 26 | public static void setDateSoureType(String dateSoureType){ 27 | CONTEXT_HOLDER.set(dateSoureType); 28 | } 29 | 30 | /** 31 | * @Title: getDateSoureType 32 | * @Description: 获得数据源的变量 33 | * @return String 34 | * @throws 35 | */ 36 | public static String getDateSoureType(){ 37 | return CONTEXT_HOLDER.get(); 38 | } 39 | 40 | /** 41 | * @Title: clearDateSoureType 42 | * @Description: 清空所有的数据源变量 43 | * @return void 44 | * @throws 45 | */ 46 | public static void clearDateSoureType(){ 47 | CONTEXT_HOLDER.remove(); 48 | } 49 | 50 | /** 51 | * @Title: existDateSoure 52 | * @Description: 判断数据源是否已存在 53 | * @param dateSoureType 54 | * @return boolean 55 | * @throws 56 | */ 57 | public static boolean existDateSoure(String dateSoureType ){ 58 | return datasourceId.contains(dateSoureType); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/rick/aspect/DynamicDataSourceAspect.java: -------------------------------------------------------------------------------- 1 | package com.rick.aspect; 2 | 3 | import com.rick.annotation.DataSourceAnnotation; 4 | import com.rick.datasource.DynamicDataSourceContextHolder; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | import org.aspectj.lang.JoinPoint; 8 | import org.aspectj.lang.annotation.After; 9 | import org.aspectj.lang.annotation.Aspect; 10 | import org.aspectj.lang.annotation.Before; 11 | import org.springframework.core.annotation.Order; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Aspect 15 | @Order(-1) 16 | @Component 17 | public class DynamicDataSourceAspect { 18 | 19 | private static final Logger logger = LogManager.getLogger(DynamicDataSourceAspect.class); 20 | 21 | /** 22 | * 切换数据库 23 | * @param point 24 | * @param dataSourceAnnotation 25 | * @return 26 | * @throws Throwable 27 | */ 28 | @Before("@annotation(dataSourceAnnotation)") 29 | public void changeDataSource(JoinPoint point, DataSourceAnnotation dataSourceAnnotation){ 30 | Object[] methodArgs = point.getArgs(); 31 | String dsId = methodArgs[methodArgs.length-1].toString(); 32 | 33 | if(!DynamicDataSourceContextHolder.existDateSoure(dsId)){ 34 | logger.error("No data source found ...【"+dsId+"】"); 35 | return; 36 | }else{ 37 | DynamicDataSourceContextHolder.setDateSoureType(dsId); 38 | } 39 | } 40 | 41 | /** 42 | * @Title: destroyDataSource 43 | * @Description: 销毁数据源 在所有的方法执行执行完毕后 44 | * @param point 45 | * @param dataSourceAnnotation 46 | * @return void 47 | * @throws 48 | */ 49 | @After("@annotation(dataSourceAnnotation)") 50 | public void destroyDataSource(JoinPoint point,DataSourceAnnotation dataSourceAnnotation){ 51 | DynamicDataSourceContextHolder.clearDateSoureType(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./logs/ 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/main/java/com/rick/config/SessionFactoryConfig.java: -------------------------------------------------------------------------------- 1 | package com.rick.config; 2 | 3 | import org.apache.commons.lang3.exception.ExceptionUtils; 4 | import org.apache.ibatis.session.SqlSessionFactory; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | import org.mybatis.spring.SqlSessionFactoryBean; 8 | import org.mybatis.spring.SqlSessionTemplate; 9 | import org.mybatis.spring.annotation.MapperScan; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 14 | import org.springframework.core.io.support.ResourcePatternResolver; 15 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 16 | import org.springframework.transaction.PlatformTransactionManager; 17 | import org.springframework.transaction.annotation.EnableTransactionManagement; 18 | 19 | import javax.sql.DataSource; 20 | import java.io.IOException; 21 | 22 | @Configuration 23 | @EnableTransactionManagement 24 | @MapperScan("com.rick.mappers") 25 | public class SessionFactoryConfig { 26 | 27 | private static Logger logger = LogManager.getLogger(SessionFactoryConfig.class); 28 | 29 | @Autowired 30 | private DataSource dataSource; 31 | 32 | private String typeAliasPackage = "com.rick.entities"; 33 | 34 | /** 35 | *创建sqlSessionFactoryBean 实例 36 | * 并且设置configtion 如驼峰命名.等等 37 | * 设置mapper 映射路径 38 | * 设置datasource数据源 39 | * @return 40 | */ 41 | @Bean(name = "sqlSessionFactory") 42 | public SqlSessionFactoryBean createSqlSessionFactoryBean() { 43 | logger.info("createSqlSessionFactoryBean method"); 44 | 45 | try{ 46 | ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 47 | SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); 48 | sqlSessionFactoryBean.setDataSource(dataSource); 49 | sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:com/rick/mappers/*.xml")); 50 | sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasPackage); 51 | return sqlSessionFactoryBean; 52 | } 53 | catch(IOException ex){ 54 | logger.error("Error happens when getting config files." + ExceptionUtils.getMessage(ex)); 55 | } 56 | return null; 57 | } 58 | 59 | @Bean 60 | public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { 61 | return new SqlSessionTemplate(sqlSessionFactory); 62 | } 63 | 64 | @Bean 65 | public PlatformTransactionManager annotationDrivenTransactionManager() { 66 | return new DataSourceTransactionManager(dataSource); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/rick/mappers/employeeMapper.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 27 | 28 | 29 | insert into student(NAME,CLASS_NAME,CREATE_DATE,UPDATE_DATE) values(#{name},#{className},CURRENT_TIMESTAMP(),CURRENT_TIMESTAMP()) 30 | 31 | 32 | 33 | insert into student( 34 | NAME, 35 | CLASS_NAME, 36 | CREATE_DATE, 37 | UPDATE_DATE) 38 | values 39 | 40 | ( 41 | #{item.name}, 42 | #{item.className}, 43 | CURRENT_TIMESTAMP(), 44 | CURRENT_TIMESTAMP() 45 | ) 46 | 47 | 48 | 49 | 50 | delete from student where ID=#{id} 51 | 52 | 53 | 54 | delete from student 55 | 56 | 57 | NAME like #{name} 58 | 59 | 60 | AND CLASS_NAME like #{className} 61 | 62 | 63 | 64 | 65 | 66 | update student set 67 | NAME = #{name}, 68 | CLASS_NAME = #{className}, 69 | UPDATE_DATE = CURRENT_TIMESTAMP() 70 | where id = #{id} 71 | 72 | -------------------------------------------------------------------------------- /src/main/java/com/rick/common/AppConstants.java: -------------------------------------------------------------------------------- 1 | package com.rick.common; 2 | 3 | public class AppConstants { 4 | 5 | private AppConstants() { 6 | 7 | } 8 | 9 | public static final String DATA_SOURCE_PREfIX_CUSTOM="spring.custom.datasource."; 10 | 11 | public static final String DATA_SOURCE_CUSTOM_NAME="name"; 12 | 13 | public static final String SEP = ","; 14 | public static final String DRUID_SOURCE_PREFIX = "spring.datasource.druid."; 15 | 16 | public static final String DATA_SOURCE_TYPE = "type"; 17 | public static final String DATA_SOURCE_DRIVER = "driver-class-name"; 18 | public static final String DATA_SOURCE_URL = "url"; 19 | public static final String DATA_SOURCE_USER_NAME = "username"; 20 | public static final String DATA_SOURCE_PASSWORD = "password"; 21 | 22 | public static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource"; 23 | 24 | 25 | public static final String RESULT_CONSTANTS = "result"; 26 | public static final String SUCCESS_RESULT = "SUCCESS"; 27 | public static final String FAIL_RESULT = "FAIL"; 28 | public static final String MESSAGE_CONSTANTS = "message"; 29 | 30 | public static final String STUDENTS_NODE_NAME = "students"; 31 | public static final String STUDENT_NODE_NAME = "student"; 32 | 33 | public static final String ID_ATTRIBUTE_NAME = "id"; 34 | public static final String NAME_ATTRIBUTE_NAME = "name"; 35 | public static final String CLASS_ATTRIBUTE_NAME = "className"; 36 | public static final String CRAETE_DATE_ATTRIBUTE_NAME = "createDate"; 37 | public static final String UPDATE_DATE_ATTRIBUTE_NAME = "updateDate"; 38 | public static final String ENABLED_ATTRIBUTE_NAME = "enabled"; 39 | public static final String DB_TYPE = "dbType"; 40 | 41 | public static final String ID_JSON_PATH = "$.id"; 42 | public static final String STUDENT_ID_JSON_PATH = "$.student.id"; 43 | public static final String NAME_JSON_PATH = "$.name"; 44 | public static final String STUDENT_NAME_JSON_PATH = "$.student.name"; 45 | public static final String CLASS_JSON_PATH = "$.className"; 46 | public static final String STUDENT_CLASS_JSON_PATH = "$.student.className"; 47 | public static final String RESULT_JSON_PATH = "$.result"; 48 | public static final String MESSAGE_JSON_PATH = "$.message"; 49 | public static final String STUDENTS_JSON_PATH = "$.students"; 50 | public static final String NEW_NAME_JSON_PATH = "$.newName"; 51 | public static final String NEW_CLASS_JSON_PATH = "$.newClassName"; 52 | 53 | public static final String STUDENT_NOT_EXIST = "The student having id %d does not exist!"; 54 | public static final String STUDENTS_NOT_EXIST = "No matched students"; 55 | 56 | 57 | public static final String ADD_STUDENT_API_URL = "/api/addStudent"; 58 | public static final String ADD_STUDENTS_API_URL = "/api/addStudents"; 59 | public static final String DELETE_STUDENT_API_URL = "/api/deleteStudent"; 60 | public static final String UPDATE_STUDENT_API_URL = "/api/updateStudent"; 61 | public static final String GET_STUDENT_API_URL = "/api/getStudent"; 62 | public static final String GET_STUDENTS_API_URL = "/api/getStudents"; 63 | public static final String DELETE_STUDENTS_API_URL = "/api/deleteStudents"; 64 | } 65 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.custom.datasource.name=db1,db2,db3 2 | 3 | spring.custom.datasource.db1.name=db1 4 | spring.custom.datasource.db1.type=com.alibaba.druid.pool.DruidDataSource 5 | spring.custom.datasource.db1.driver-class-name=com.mysql.jdbc.Driver 6 | spring.custom.datasource.db1.url=jdbc:mysql://localhost:3306/testdb_1?characterEncoding=utf8&autoReconnect=true&useSSL=false&useAffectedRows=true 7 | spring.custom.datasource.db1.username=appuser1 8 | spring.custom.datasource.db1.password=admin 9 | 10 | spring.custom.datasource.db2.name=db2 11 | spring.custom.datasource.db2.type=com.alibaba.druid.pool.DruidDataSource 12 | spring.custom.datasource.db2.driver-class-name=com.mysql.jdbc.Driver 13 | spring.custom.datasource.db2.url=jdbc:mysql://localhost:3306/testdb_2?characterEncoding=utf8&autoReconnect=true&useSSL=false&useAffectedRows=true 14 | spring.custom.datasource.db2.username=appuser2 15 | spring.custom.datasource.db2.password=admin 16 | 17 | spring.custom.datasource.db3.name=db3 18 | spring.custom.datasource.db3.type=com.alibaba.druid.pool.DruidDataSource 19 | spring.custom.datasource.db3.driver-class-name=com.mysql.jdbc.Driver 20 | spring.custom.datasource.db3.url=jdbc:mysql://localhost:3306/testdb_3?characterEncoding=utf8&autoReconnect=true&useSSL=false&useAffectedRows=true 21 | spring.custom.datasource.db3.username=appuser3 22 | spring.custom.datasource.db3.password=admin 23 | 24 | 25 | spring.datasource.druid.initial-size=5 26 | spring.datasource.druid.min-idle=5 27 | spring.datasource.druid.async-init=true 28 | spring.datasource.druid.async-close-connection-enable=true 29 | spring.datasource.druid.max-active=20 30 | spring.datasource.druid.max-wait=60000 31 | spring.datasource.druid.time-between-eviction-runs-millis=60000 32 | spring.datasource.druid.min-evictable-idle-time-millis=30000 33 | spring.datasource.druid.validation-query=SELECT 1 FROM DUAL 34 | spring.datasource.druid.test-while-idle=true 35 | spring.datasource.druid.test-on-borrow=false 36 | spring.datasource.druid.test-on-return=false 37 | spring.datasource.druid.pool-prepared-statements=true 38 | spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20 39 | spring.datasource.druid.filters=stat,wall,log4j2 40 | spring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 41 | 42 | spring.datasource.druid.web-stat-filter.enabled=true 43 | spring.datasource.druid.web-stat-filter.url-pattern=/* 44 | spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/* 45 | spring.datasource.druid.web-stat-filter.session-stat-enable=true 46 | spring.datasource.druid.web-stat-filter.profile-enable=true 47 | 48 | 49 | spring.datasource.druid.stat-view-servlet.enabled=true 50 | spring.datasource.druid.stat-view-servlet.url-pattern=/druid/* 51 | spring.datasource.druid.stat-view-servlet.login-username=admin 52 | spring.datasource.druid.stat-view-servlet.login-password=admin 53 | spring.datasource.druid.stat-view-servlet.reset-enable=false 54 | spring.datasource.druid.stat-view-servlet.allow=127.0.0.1 55 | 56 | spring.datasource.druid.filter.wall.enabled=true 57 | spring.datasource.druid.filter.wall.db-type=mysql 58 | spring.datasource.druid.filter.wall.config.alter-table-allow=false 59 | spring.datasource.druid.filter.wall.config.truncate-allow=false 60 | spring.datasource.druid.filter.wall.config.drop-table-allow=false 61 | 62 | spring.datasource.druid.filter.wall.config.none-base-statement-allow=false 63 | spring.datasource.druid.filter.wall.config.update-where-none-check=true 64 | spring.datasource.druid.filter.wall.config.select-into-outfile-allow=false 65 | spring.datasource.druid.filter.wall.config.metadata-allow=true 66 | spring.datasource.druid.filter.wall.log-violation=true 67 | spring.datasource.druid.filter.wall.throw-exception=true 68 | 69 | spring.datasource.druid.filter.stat.log-slow-sql= true 70 | spring.datasource.druid.filter.stat.slow-sql-millis=1000 71 | spring.datasource.druid.filter.stat.merge-sql=true 72 | spring.datasource.druid.filter.stat.db-type=mysql 73 | spring.datasource.druid.filter.stat.enabled=true 74 | 75 | 76 | spring.datasource.druid.filter.log4j2.enabled=true 77 | spring.datasource.druid.filter.log4j2.connection-log-enabled=true 78 | spring.datasource.druid.filter.log4j2.connection-close-after-log-enabled=true 79 | spring.datasource.druid.filter.log4j2.connection-commit-after-log-enabled=true 80 | spring.datasource.druid.filter.log4j2.connection-connect-after-log-enabled=true 81 | spring.datasource.druid.filter.log4j2.connection-connect-before-log-enabled=true 82 | spring.datasource.druid.filter.log4j2.connection-log-error-enabled=true 83 | spring.datasource.druid.filter.log4j2.data-source-log-enabled=true 84 | spring.datasource.druid.filter.log4j2.result-set-log-enabled=true 85 | spring.datasource.druid.filter.log4j2.statement-log-enabled=true -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.rick 7 | SpringBootDruidMultiDB 8 | 0.1.0 9 | jar 10 | 11 | SpringBootDruidMultiDB 12 | 测试SpringBoot+Mybatis+Druid的集成 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.9.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | com.alibaba 30 | druid-spring-boot-starter 31 | 1.1.6 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-logging 36 | 37 | 38 | 39 | 40 | org.mybatis.spring.boot 41 | mybatis-spring-boot-starter 42 | 1.3.1 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-logging 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-web 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-logging 58 | 59 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-test 65 | test 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-logging 70 | 71 | 72 | 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-starter-aop 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-starter-logging 81 | 82 | 83 | 84 | 85 | 86 | org.apache.logging.log4j 87 | log4j-api 88 | 2.10.0 89 | 90 | 91 | org.apache.logging.log4j 92 | log4j-core 93 | 2.10.0 94 | 95 | 96 | com.lmax 97 | disruptor 98 | 3.3.7 99 | 100 | 101 | mysql 102 | mysql-connector-java 103 | 5.1.45 104 | 105 | 106 | com.alibaba 107 | fastjson 108 | 1.2.43 109 | 110 | 111 | org.apache.commons 112 | commons-lang3 113 | 3.7 114 | 115 | 116 | org.apache.commons 117 | commons-collections4 118 | 4.1 119 | 120 | 121 | commons-logging 122 | commons-logging 123 | 1.2 124 | 125 | 126 | 127 | 128 | 129 | 130 | org.springframework.boot 131 | spring-boot-maven-plugin 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/main/java/com/rick/controller/ApiController.java: -------------------------------------------------------------------------------- 1 | package com.rick.controller; 2 | 3 | import static com.rick.common.AppConstants.CLASS_ATTRIBUTE_NAME; 4 | import static com.rick.common.AppConstants.CRAETE_DATE_ATTRIBUTE_NAME; 5 | import static com.rick.common.AppConstants.DB_TYPE; 6 | import static com.rick.common.AppConstants.FAIL_RESULT; 7 | import static com.rick.common.AppConstants.ID_ATTRIBUTE_NAME; 8 | import static com.rick.common.AppConstants.MESSAGE_CONSTANTS; 9 | import static com.rick.common.AppConstants.NAME_ATTRIBUTE_NAME; 10 | import static com.rick.common.AppConstants.RESULT_CONSTANTS; 11 | import static com.rick.common.AppConstants.STUDENT_NODE_NAME; 12 | import static com.rick.common.AppConstants.SUCCESS_RESULT; 13 | import static com.rick.common.AppConstants.UPDATE_DATE_ATTRIBUTE_NAME; 14 | 15 | import java.text.SimpleDateFormat; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | import org.apache.commons.lang3.StringUtils; 20 | import org.apache.commons.lang3.exception.ExceptionUtils; 21 | import org.apache.logging.log4j.LogManager; 22 | import org.apache.logging.log4j.Logger; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.http.HttpHeaders; 25 | import org.springframework.http.HttpStatus; 26 | import org.springframework.http.MediaType; 27 | import org.springframework.http.ResponseEntity; 28 | import org.springframework.web.bind.annotation.RequestBody; 29 | import org.springframework.web.bind.annotation.RequestMapping; 30 | import org.springframework.web.bind.annotation.RequestMethod; 31 | import org.springframework.web.bind.annotation.RestController; 32 | 33 | import com.alibaba.fastjson.JSON; 34 | import com.alibaba.fastjson.JSONObject; 35 | import com.rick.entities.Student; 36 | import com.rick.service.StudentService; 37 | 38 | @RestController 39 | public class ApiController { 40 | 41 | private Logger logger = LogManager.getLogger(ApiController.class); 42 | 43 | private SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); 44 | 45 | @Autowired 46 | private StudentService studentService; 47 | 48 | /** 49 | * 添加学生并检查 50 | * @param params 添加的学生信息参数 51 | * @return 添加学生后检查的结果,如果成功返回Success和添加学生信息,失败返回Fail和失败信息。 52 | */ 53 | @RequestMapping(value = "/api/addStudent", produces = "application/json", method = RequestMethod.POST) 54 | public ResponseEntity addStudent(@RequestBody String params) 55 | { 56 | ResponseEntity response = null; 57 | HttpHeaders headers = new HttpHeaders(); 58 | headers.setContentType(MediaType.APPLICATION_JSON_UTF8); 59 | 60 | Map resultMap = new HashMap<>(); 61 | try{ 62 | 63 | JSONObject paramJsonObj = JSON.parseObject(params); 64 | 65 | Student student = new Student(); 66 | student.setName(paramJsonObj.getString(NAME_ATTRIBUTE_NAME)); 67 | student.setClassName(paramJsonObj.getString(CLASS_ATTRIBUTE_NAME)); 68 | String dbType = paramJsonObj.getString(DB_TYPE); 69 | if(StringUtils.isEmpty(dbType)) 70 | { 71 | dbType = "db1"; 72 | } 73 | 74 | int id = studentService.insertStudent(student,dbType); 75 | 76 | if(id <= 0) 77 | { 78 | String errorMessage = "Error happens when insert student, the insert operation failed."; 79 | logger.error(errorMessage); 80 | resultMap = new HashMap<>(); 81 | resultMap.put(RESULT_CONSTANTS, FAIL_RESULT); 82 | resultMap.put(MESSAGE_CONSTANTS, errorMessage); 83 | String resultStr = JSON.toJSONString(resultMap); 84 | 85 | return new ResponseEntity<>(resultStr,headers, HttpStatus.INTERNAL_SERVER_ERROR); 86 | } 87 | else 88 | { 89 | resultMap.put(RESULT_CONSTANTS, SUCCESS_RESULT); 90 | 91 | Student newStudent = studentService.findStudentById(id,dbType); 92 | 93 | Map studentMap = new HashMap<>(); 94 | studentMap.put(ID_ATTRIBUTE_NAME,Integer.toString(id)); 95 | studentMap.put(NAME_ATTRIBUTE_NAME,newStudent.getName()); 96 | studentMap.put(CLASS_ATTRIBUTE_NAME,newStudent.getClassName()); 97 | studentMap.put(CRAETE_DATE_ATTRIBUTE_NAME, sdf.format(newStudent.getCreateDate())); 98 | studentMap.put(UPDATE_DATE_ATTRIBUTE_NAME, sdf.format(newStudent.getUpdateDate())); 99 | resultMap.put(STUDENT_NODE_NAME,studentMap); 100 | 101 | String resultStr = JSON.toJSONString(resultMap); 102 | response = new ResponseEntity<>(resultStr,headers, HttpStatus.OK); 103 | } 104 | } 105 | catch(Exception ex) 106 | { 107 | response = processException("insert student", ex, headers); 108 | } 109 | return response; 110 | } 111 | 112 | private ResponseEntity processException(String operationName, Exception ex, HttpHeaders headers) 113 | { 114 | String errorMessageTemplate = "Error happens when %s.The exception info is:" + ExceptionUtils.getMessage(ex); 115 | String errorMessage = String.format(errorMessageTemplate,operationName); 116 | logger.error(errorMessage); 117 | Map resultMap = new HashMap<>(); 118 | resultMap.put(RESULT_CONSTANTS, FAIL_RESULT); 119 | resultMap.put(MESSAGE_CONSTANTS, ex.getMessage()); 120 | String resultStr = JSON.toJSONString(resultMap); 121 | 122 | return new ResponseEntity<>(resultStr,headers, HttpStatus.INTERNAL_SERVER_ERROR); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/rick/config/DruidConfig.java: -------------------------------------------------------------------------------- 1 | package com.rick.config; 2 | 3 | import com.alibaba.druid.filter.Filter; 4 | import com.alibaba.druid.pool.DruidDataSource; 5 | import com.alibaba.druid.support.http.StatViewServlet; 6 | import com.alibaba.druid.support.http.WebStatFilter; 7 | import com.rick.common.AppConstants; 8 | import com.rick.datasource.DynamicDataSource; 9 | import com.rick.datasource.DynamicDataSourceContextHolder; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | import org.springframework.beans.MutablePropertyValues; 14 | import org.springframework.boot.bind.RelaxedDataBinder; 15 | import org.springframework.boot.bind.RelaxedPropertyResolver; 16 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 17 | import org.springframework.boot.web.servlet.RegistrationBean; 18 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 19 | import org.springframework.context.EnvironmentAware; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.context.annotation.Primary; 23 | import org.springframework.core.convert.ConversionService; 24 | import org.springframework.core.convert.support.DefaultConversionService; 25 | import org.springframework.core.env.Environment; 26 | import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 27 | import org.springframework.transaction.annotation.EnableTransactionManagement; 28 | 29 | import javax.sql.DataSource; 30 | import java.util.ArrayList; 31 | import java.util.HashMap; 32 | import java.util.LinkedHashMap; 33 | import java.util.List; 34 | import java.util.Map; 35 | 36 | import static com.rick.common.AppConstants.*; 37 | 38 | @Configuration 39 | @EnableTransactionManagement 40 | public class DruidConfig implements EnvironmentAware { 41 | 42 | private List customDataSourceNames = new ArrayList<>(); 43 | 44 | private Logger logger = LogManager.getLogger(DruidConfig.class); 45 | 46 | private ConversionService conversionService = new DefaultConversionService(); 47 | 48 | private Environment environment; 49 | 50 | /** 51 | * @param environment the enviroment to set 52 | */ 53 | @Override 54 | public void setEnvironment(Environment environment) { 55 | this.environment = environment; 56 | } 57 | 58 | @Bean(name = "dataSource") 59 | @Primary 60 | public AbstractRoutingDataSource dataSource() { 61 | DynamicDataSource dynamicDataSource = new DynamicDataSource(); 62 | LinkedHashMap targetDatasources = new LinkedHashMap<>(); 63 | initCustomDataSources(targetDatasources); 64 | dynamicDataSource.setDefaultTargetDataSource(targetDatasources.get(customDataSourceNames.get(0))); 65 | dynamicDataSource.setTargetDataSources(targetDatasources); 66 | dynamicDataSource.afterPropertiesSet(); 67 | return dynamicDataSource; 68 | } 69 | 70 | private void initCustomDataSources(LinkedHashMap targetDataResources) 71 | { 72 | RelaxedPropertyResolver property = 73 | new RelaxedPropertyResolver(environment, DATA_SOURCE_PREfIX_CUSTOM); 74 | String dataSourceNames = property.getProperty(DATA_SOURCE_CUSTOM_NAME); 75 | if(StringUtils.isEmpty(dataSourceNames)) 76 | { 77 | logger.error("The multiple data source list are empty."); 78 | } 79 | else{ 80 | RelaxedPropertyResolver springDataSourceProperty = 81 | new RelaxedPropertyResolver(environment, "spring.datasource."); 82 | 83 | Map druidPropertiesMaps = springDataSourceProperty.getSubProperties("druid."); 84 | Map druidValuesMaps = new HashMap<>(); 85 | for(String key:druidPropertiesMaps.keySet()) 86 | { 87 | String druidKey = AppConstants.DRUID_SOURCE_PREFIX + key; 88 | druidValuesMaps.put(druidKey,druidPropertiesMaps.get(key)); 89 | } 90 | 91 | MutablePropertyValues dataSourcePropertyValue = new MutablePropertyValues(druidValuesMaps); 92 | 93 | for (String dataSourceName : dataSourceNames.split(SEP)) { 94 | try { 95 | Map dsMaps = property.getSubProperties(dataSourceName+"."); 96 | 97 | for(String dsKey : dsMaps.keySet()) 98 | { 99 | if(dsKey.equals("type")) 100 | { 101 | dataSourcePropertyValue.addPropertyValue("spring.datasource.type", dsMaps.get(dsKey)); 102 | } 103 | else 104 | { 105 | String druidKey = DRUID_SOURCE_PREFIX + dsKey; 106 | dataSourcePropertyValue.addPropertyValue(druidKey, dsMaps.get(dsKey)); 107 | } 108 | } 109 | 110 | DataSource ds = dataSourcebuild(dataSourcePropertyValue); 111 | if(null != ds){ 112 | if(ds instanceof DruidDataSource) 113 | { 114 | DruidDataSource druidDataSource = (DruidDataSource)ds; 115 | druidDataSource.setName(dataSourceName); 116 | initDruidFilters(druidDataSource); 117 | } 118 | 119 | customDataSourceNames.add(dataSourceName); 120 | DynamicDataSourceContextHolder.datasourceId.add(dataSourceName); 121 | targetDataResources.put(dataSourceName,ds); 122 | 123 | } 124 | logger.info("Data source initialization 【"+dataSourceName+"】 successfully ..."); 125 | } catch (Exception e) { 126 | logger.error("Data source initialization【"+dataSourceName+"】 failed ...", e); 127 | } 128 | } 129 | } 130 | } 131 | 132 | 133 | /** 134 | * @Title: DataSourcebuild 135 | * @Description: 创建数据源 136 | * @param dataSourcePropertyValue 数据源创建所需参数 137 | * 138 | * @return DataSource 创建的数据源对象 139 | */ 140 | public DataSource dataSourcebuild(MutablePropertyValues dataSourcePropertyValue) 141 | { 142 | DataSource ds = null; 143 | 144 | if(dataSourcePropertyValue.isEmpty()){ 145 | return ds; 146 | } 147 | 148 | String type = dataSourcePropertyValue.get("spring.datasource.type").toString(); 149 | 150 | if(StringUtils.isNotEmpty(type)) 151 | { 152 | if(StringUtils.equals(type,DruidDataSource.class.getTypeName())) 153 | { 154 | ds = new DruidDataSource(); 155 | 156 | RelaxedDataBinder dataBinder = new RelaxedDataBinder(ds, DRUID_SOURCE_PREFIX); 157 | dataBinder.setConversionService(conversionService); 158 | dataBinder.setIgnoreInvalidFields(false); 159 | dataBinder.setIgnoreNestedProperties(false); 160 | dataBinder.setIgnoreUnknownFields(true); 161 | dataBinder.bind(dataSourcePropertyValue); 162 | } 163 | } 164 | return ds; 165 | } 166 | 167 | @Bean 168 | public ServletRegistrationBean statViewServlet(){ 169 | 170 | RelaxedPropertyResolver property = 171 | new RelaxedPropertyResolver(environment, "spring.datasource.druid."); 172 | 173 | Map druidPropertiesMaps = property.getSubProperties("stat-view-servlet."); 174 | 175 | 176 | boolean statViewServletEnabled = false; 177 | String statViewServletEnabledKey = ENABLED_ATTRIBUTE_NAME; 178 | ServletRegistrationBean registrationBean = null; 179 | 180 | if(druidPropertiesMaps.containsKey(statViewServletEnabledKey)) 181 | { 182 | String statViewServletEnabledValue = 183 | druidPropertiesMaps.get(statViewServletEnabledKey).toString(); 184 | statViewServletEnabled = Boolean.parseBoolean(statViewServletEnabledValue); 185 | } 186 | if(statViewServletEnabled){ 187 | registrationBean = new ServletRegistrationBean(); 188 | StatViewServlet statViewServlet = new StatViewServlet(); 189 | 190 | registrationBean.setServlet(statViewServlet); 191 | 192 | String urlPatternKey= "url-pattern"; 193 | String allowKey= "allow"; 194 | String denyKey= "deny"; 195 | String usernameKey= "login-username"; 196 | String secretKey = "login-password"; 197 | String resetEnableKey= "reset-enable"; 198 | 199 | if(druidPropertiesMaps.containsKey(urlPatternKey)){ 200 | String urlPatternValue = 201 | druidPropertiesMaps.get(urlPatternKey).toString(); 202 | registrationBean.addUrlMappings(urlPatternValue); 203 | } 204 | else 205 | { 206 | registrationBean.addUrlMappings("/druid/*"); 207 | } 208 | 209 | addBeanParameter(druidPropertiesMaps,registrationBean, "allow",allowKey); 210 | addBeanParameter(druidPropertiesMaps,registrationBean, "deny",denyKey); 211 | addBeanParameter(druidPropertiesMaps,registrationBean, "loginUsername",usernameKey); 212 | addBeanParameter(druidPropertiesMaps,registrationBean, "loginPassword",secretKey); 213 | addBeanParameter(druidPropertiesMaps,registrationBean, "resetEnable",resetEnableKey); 214 | } 215 | 216 | return registrationBean; 217 | } 218 | 219 | @Bean 220 | public FilterRegistrationBean filterRegistrationBean(){ 221 | RelaxedPropertyResolver property = 222 | new RelaxedPropertyResolver(environment, "spring.datasource.druid."); 223 | 224 | Map druidPropertiesMaps = property.getSubProperties("web-stat-filter."); 225 | 226 | 227 | boolean webStatFilterEnabled = false; 228 | String webStatFilterEnabledKey = ENABLED_ATTRIBUTE_NAME; 229 | FilterRegistrationBean registrationBean = null; 230 | if(druidPropertiesMaps.containsKey(webStatFilterEnabledKey)) 231 | { 232 | String webStatFilterEnabledValue = 233 | druidPropertiesMaps.get(webStatFilterEnabledKey).toString(); 234 | webStatFilterEnabled = Boolean.parseBoolean(webStatFilterEnabledValue); 235 | } 236 | if(webStatFilterEnabled){ 237 | registrationBean = new FilterRegistrationBean(); 238 | WebStatFilter filter = new WebStatFilter(); 239 | registrationBean.setFilter(filter); 240 | 241 | String urlPatternKey = "url-pattern"; 242 | String exclusionsKey = "exclusions"; 243 | String sessionStatEnabledKey = "session-stat-enable"; 244 | String profileEnabledKey = "profile-enable"; 245 | String principalCookieNameKey = "principal-cookie-name"; 246 | String principalSessionNameKey = "principal-session-name"; 247 | String sessionStateMaxCountKey = "session-stat-max-count"; 248 | 249 | if(druidPropertiesMaps.containsKey(urlPatternKey)){ 250 | String urlPatternValue = 251 | druidPropertiesMaps.get(urlPatternKey).toString(); 252 | registrationBean.addUrlPatterns(urlPatternValue); 253 | } 254 | else{ 255 | registrationBean.addUrlPatterns("/*"); 256 | } 257 | 258 | if(druidPropertiesMaps.containsKey(exclusionsKey)){ 259 | String exclusionsValue = 260 | druidPropertiesMaps.get(exclusionsKey).toString(); 261 | registrationBean.addInitParameter("exclusions",exclusionsValue); 262 | } 263 | else{ 264 | registrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); 265 | } 266 | 267 | addBeanParameter(druidPropertiesMaps,registrationBean, "sessionStatEnable",sessionStatEnabledKey); 268 | addBeanParameter(druidPropertiesMaps,registrationBean, "profileEnable",profileEnabledKey); 269 | addBeanParameter(druidPropertiesMaps,registrationBean, "principalCookieName",principalCookieNameKey); 270 | addBeanParameter(druidPropertiesMaps,registrationBean, "sessionStatMaxCount",sessionStateMaxCountKey); 271 | addBeanParameter(druidPropertiesMaps,registrationBean, "principalSessionName",principalSessionNameKey); 272 | } 273 | return registrationBean; 274 | } 275 | 276 | private void addBeanParameter(Map druidPropertyMap, RegistrationBean registrationBean, String paramName, String propertyKey){ 277 | if(druidPropertyMap.containsKey(propertyKey)){ 278 | String propertyValue = 279 | druidPropertyMap.get(propertyKey).toString(); 280 | registrationBean.addInitParameter(paramName, propertyValue); 281 | } 282 | } 283 | 284 | private void initDruidFilters(DruidDataSource druidDataSource){ 285 | 286 | List filters = druidDataSource.getProxyFilters(); 287 | 288 | RelaxedPropertyResolver filterProperty = 289 | new RelaxedPropertyResolver(environment, "spring.datasource.druid.filter."); 290 | 291 | 292 | String filterNames= environment.getProperty("spring.datasource.druid.filters"); 293 | 294 | String[] filterNameArray = filterNames.split("\\,"); 295 | 296 | for(int i=0; i filterValueMap = filterProperty.getSubProperties(filterName + "."); 301 | String statFilterEnabled = filterValueMap.get(ENABLED_ATTRIBUTE_NAME).toString(); 302 | if(statFilterEnabled.equals("true")){ 303 | MutablePropertyValues propertyValues = new MutablePropertyValues(filterValueMap); 304 | RelaxedDataBinder dataBinder = new RelaxedDataBinder(filter); 305 | dataBinder.bind(propertyValues); 306 | } 307 | } 308 | } 309 | } 310 | --------------------------------------------------------------------------------