springboot+jpa+mybatis 多數據源支持
配置dataSource
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
* com.ehaoyao.paycenter.job.common.config
* 數據源配置類
* @author PF
* @create 2018-05-10 16:17
**/
@Configuration
public class DataSourceConfig {
@Bean(name = "payCenterDataSource")
@Qualifier("payCenterDataSource")
@Primary
@ConfigurationProperties(prefix="spring.datasource.paycenter")
public DataSource paycenterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "erpDataSource")
@Qualifier("erpDataSource")
@ConfigurationProperties(prefix="spring.datasource.erp")
public DataSource erpDataSource() {
return DataSourceBuilder.create().build();
}
}
master數據源的sessionFactory、transactionManager等配置
package com.ehaoyao.paycenter.job.common.config;/**
* 支付中心數據源配置類
*
* @author PF
* Created by dell on 2018-05-04.
*/
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
/**
* com.ehaoyao.paycenter.job.common.config
* 支付中心數據源配置類
*
* @author PF
* @create 2018-05-04 10:26
**/
@Configuration
@MapperScan(basePackages = "com.ehaoyao.paycenter.persistence.pay.mapper.paycenter",
sqlSessionFactoryRef = "payCenterSqlSessionFactory")
@EnableTransactionManagement
public class PayCenterDataSourceConfig {
static final String MAPPER_LOCATION = "classpath:mappings/com/ehaoyao/paycenter
/persistence/pay/mapper/paycenter/*.xml";
@Autowired
@Qualifier("payCenterDataSource")
private DataSource payCenterDataSource;
@Bean(name = "payCenterTransactionManager")
@Primary
public DataSourceTransactionManager masterTransactionManager() {
return new DataSourceTransactionManager(payCenterDataSource);
}
@Bean(name = "payCenterSqlSessionFactory")
@Primary
public SqlSessionFactory payCenterSqlSessionFactory(@Qualifier("payCenterDataSource")
DataSource payCenterDataSource)
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(payCenterDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(PayCenterDataSourceConfig.MAPPER_LOCATION));
return sessionFactory.getObject();
}
}
配置slave數據源的sessionFactory、transactionManager等配置。
(其中需要注意的是,配置多數據源后,spring.jpa 這些配置,就不要寫在application里面了,沒法對應多個啊,所以需要寫在配置類中,如上getVendorProperties方法)
自己留着備查用的,master數據源用的mybatis,slave用的jpa(雖然用法很搞,但是主要為了記錄多數據源,及springboot下jpa配置相關)
package com.ehaoyao.paycenter.job.common.config;/**
* ERP數據源配置類
*
* @author PF
* Created by dell on 2018-05-04.
*/
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import java.util.Map;
/**
* com.ehaoyao.paycenter.job.common.config
* ERP數據源配置類
* @author PF
* @create 2018-05-04 10:27
**/
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="erpEntityManagerFactory",
transactionManagerRef="erpTransactionManager",
basePackages= { "com.ehaoyao.paycenter.persistence.pay.Repository" })
public class ErpDataSourceConfig {
@Autowired
@Qualifier("erpDataSource")
private DataSource erpDataSource;
@Bean(name = "entityManager")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return erpEntityManagerFactory(builder).getObject().createEntityManager();
}
@Bean(name = "erpEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean erpEntityManagerFactory
(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(erpDataSource)
.properties(getVendorProperties(erpDataSource))
.packages("com.ehaoyao.paycenter.persistence.pay.entity.erp")
.persistenceUnit("erpPersistenceUnit")
.build();
}
@Autowired
private JpaProperties jpaProperties;
private Map getVendorProperties(DataSource dataSource) {
map.put("hibernate.dialect","org.hibernate.dialect.H2Dialect");
map.put("hibernate.hbm2ddl.auto","update");
map.put("spring.jpa.show-sql","true");
jpaProperties.setProperties(map);
return jpaProperties.getHibernateProperties(dataSource);
}
@Bean(name = "erpTransactionManager")
public PlatformTransactionManager transactionManagerPrimary
(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(erpEntityManagerFactory(builder).getObject());
}
}
踩坑爬坑
指定實體類包的位置
@Primary
@Bean(name = "payCenterEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
payCenterEntityManagerFactory(EntityManagerFactoryBuilder builder)
{
return builder
.dataSource(payCenterDataSource)
.properties(getVendorProperties(payCenterDataSource))
.packages("com.ehaoyao.paycenter.persistence.pay.entity",
"com.ehaoyao.pay.common.model")
.persistenceUnit("payCenterPersistenceUnit")
.build();
}
指定多個的話語法為packages("第一個","第二個") 。其中源碼如下: String... 為可變長參數
public EntityManagerFactoryBuilder.Builder packages(String... packagesToScan) {
this.packagesToScan = packagesToScan;
return this;
}
JPA常用注解,及注意事項
-
使用jpa的save新增數據后,有些數據庫設置了默認值的字段沒有生效,要使其生效可以在對應entity上增加 @DynamicInsert(true)
-
不想持久化的字段,比如有些字段只是實體類中臨時存儲,或僅為前端展示用,不需要在自動生成的insert、select語句中包含改字段,可以在實體類的改字段上添加 @Transient注解
-
springboot jpa自帶的分頁起始頁為0 ,但是一般前端顯示的時候,都是從1開始的。主動-1,解決方法很low,不知道有沒有好的辦法。
Pageable pageable = new PageRequest(param.getPageIndex()==0?0:param.getPageIndex()-1, param.getPageSize(), sort); //jpa自帶的分頁是從第0頁開始的,因此這里將傳過來的頁碼-1
-
使用jpa在生產環境需要注意的配置
ddl-auto:create----每次運行該程序,沒有表格會新建表格,表內有數據會清空
ddl-auto:create-drop----每次程序結束的時候會清空表
ddl-auto:update----每次運行程序,沒有表格會新建表格,表內有數據不會清空,只會更新
ddl-auto:validate----運行程序會校驗數據與數據庫的字段類型是否相同,不同會報錯
jpa支持時間范圍,及動態條件分頁查詢
之前的實例查詢雖然可以支持動態條件,而且使用方便,但是對於一個字段需要傳入多個參數的就不行了,比如 查詢某個時間范
圍內的數據。create between beginDate and endDate 。像這種需求,在真實案例中是很常見的。但是在網上找了一圈之
后,大多數是建議用@Query或者specification來實現。但要是這樣的話,我還不如用mybatis呢。。。。。(而且基本都是這
篇文章的轉載,
嚴重吐槽,百度前幾頁全是這個。。。。。原諒我已經不知道原作者是誰了,隨便貼了一個鏈接,反正都一樣。。)
之前實例查詢代碼如下:
@Override
public Page<RefundEntity> getDataList(RefundManEntity param) {
ExampleMatcher exampleMatcher =
ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING).
withMatcher("createTime", ExampleMatcher.GenericPropertyMatchers.startsWith())
.withMatcher("orderNume", ExampleMatcher.GenericPropertyMatchers.contains())
.withMatcher("refundStatus", ExampleMatcher.GenericPropertyMatchers.contains());
RefundEntity refundEntity=new RefundEntity();
BeanUtils.copyProperties(param,refundEntity);
Example<RefundEntity> example = Example.of(refundEntity, exampleMatcher);
Sort sort = new Sort(Sort.Direction.DESC, "id");
Pageable pageable = new PageRequest(param.getPageIndex()==0?0:param.getPageIndex()-1,
param.getPageSize(), sort); //jpa自帶的分頁是從第0頁開始的,因此這里將傳過來的頁碼-1
Page<RefundEntity> pages = refundManageRepository.findAll(example, pageable);
return pages;
}
我自己的low解決方案,代碼如下:
@Override
public Page<RefundEntity> getDataList(RefundManEntity param) {
Specification<RefundEntity> querySpecifi = new Specification<RefundEntity>() {
@Override
public Predicate toPredicate(Root<RefundEntity> root, CriteriaQuery<?> criteriaQuery,
CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(param.getBeginDate())) {
//大於或等於傳入時間
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createTime").
as(String.class),param.getBeginDate()));
}
if (StringUtils.isNotBlank(param.getEndDate())) {
//小於或等於傳入時間
predicates.add(criteriaBuilder.lessThanOrEqualTo(
root.get("createTime").as(String.class), param.getEndDate()));
}
if (param != null) {
Class<? extends RefundManEntity> clazz = param.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field tmpField : fields) {
tmpField.setAccessible(true);
try {
//不為空的查詢參數才拼查詢條件,並且要去掉額外加上的時間范圍條件
if (tmpField.get(param) != null &&
!tmpField.getName().equals("beginDate") && !tmpField.getName().equals("endDate"))
{
//只拼字符串查詢條件的,因為目前只需要按照 訂單號、退款狀態來查詢
if (tmpField.getType().equals(String.class) &&
StringUtil.isNotBlank((String)tmpField.get(param)))
{
String name = tmpField.getName();
predicates.add(criteriaBuilder.equal(
root.get(name), tmpField.get(param)));
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
// and到一起的話所有條件就是且關系,or就是或關系
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
Sort sort = new Sort(Sort.Direction.DESC, "id");
Pageable pageable = new PageRequest(param.getPageIndex() == 0 ? 0 : param.getPageIndex() - 1,
param.getPageSize(), sort);
//jpa自帶的分頁是從第0頁開始的,因此這里將傳過來的頁碼-1
return refundManageRepository.findAll(querySpecifi, pageable);
}
- repository需要多加個繼承
public interface RefundManageRepository extends JpaRepository<RefundEntity,Integer>
,JpaSpecificationExecutor<RefundEntity>{
}
- 配置主子表,一對多關系
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
@JoinColumn(name="order_id")
private List<OrderDetail> orderDetails;
- 主鍵id配置自動增長
PS:@GeneratedValue注解的strategy屬性提供四種值:
–AUTO: 主鍵由程序控制,是默認選項,不設置即此項。
–IDENTITY:主鍵由數據庫自動生成,即采用數據庫ID自增長的方式,Oracle不支持這種方式。
–SEQUENCE:通過數據庫的序列產生主鍵,通過@SequenceGenerator 注解指定序列名,mysql不支持這種方式。
–TABLE:通過特定的數據庫表產生主鍵,使用該策略可以使應用更易於數據庫移植。
-
A different object with the same identifier value was already associated with the session
實體類中主鍵id字段配置為@GeneratedValue auto,但是數據庫中本身已經有數據了,就會報這個錯。
從源碼可以看到,序列生成的方式為在數據庫中新增了一個hibernate_sequence 表,這個表中會存儲當前最大id
把這個id改的比你已存在數據中最大id還大就可以了。 -
使用springboot+jpa +h2 database的時候,生成的create table語句執行正常,但是在生成的alter table語句中會加上前綴,如下
alter table YAOJINGCAI-ADMIN-H2DB.PUBLIC.YJC_BIZ_MANAGER_INFO add column boss_no varchar(255)
開始以為是前綴問題導致的,最終證實,是由於配置文件中,url里面數據庫名稱配置,數據庫名稱中不能有中划線。(只能是子母+下划線)
改之前:url: jdbc:h2:file:~/.h2/yaojingcai-admin-h2db;AUTO_SERVER=TRUE
改之后:url: jdbc:h2:file:~/.h2/yaojingcai_admin_h2db;AUTO_SERVER=TRUE