Spring Boot 實踐 第六章 Spring data JAP在實際開發中的封裝和應用(上)
上一章簡單介紹了一下Spring boot和Spring Data JPA的整合和簡單使用. 但是在實際開發過程中, 我們發現Spring Data JPA提供的介面太簡單了,這樣就導致需要編寫大量的重複程式碼. 實際上Spring Data JPA提供了很多種擴充套件方式. 下面就介紹其中的一種.在上一章的程式碼基礎上,做一些修改,我們會發現在Spring boot 中使用Spring Data JPA 更容易一些.
由於篇幅的問題,本章分兩部分
1.首先,需要建立一個所有Domain的基類, 這個基類可以什麼都不寫,也可以寫一些基礎的欄位比如下面的例子
@Data @MappedSuperclass public abstract class TableEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) protected Long id; /** 建立時間 */ @Temporal(TemporalType.TIMESTAMP) @Column(nullable = false, updatable = false) protected Date createTime; /** 最後更新時間 */ @Temporal(TemporalType.TIMESTAMP) @Column(insertable = false) protected Date lastModifyTime; /** 版本號,用於實現樂觀鎖 */ @Version @Column(name = "version", nullable = false) protected int version; @PrePersist @PreUpdate protected void updateDate(){ if(createTime==null){ createTime = new Date(); } lastModifyTime = new Date(); } }
這個domain的抽象類描述了四個欄位, 分別是ID, 記錄建立時間, 最後更新時間, 和實現樂觀鎖的版本號.這都是ORM中常用的欄位, 並且這些欄位實現了自動更新.
2. 回憶一下上一章, 我們的Repository是繼承了JpaRepository, 我們現在需要自己擴充套件JpaRepository. 先寫一個擴充套件介面,讓它繼承JpaRepository
@NoRepositoryBean public interface BaseJpaRepository<T extends TableEntity, ID extends Serializable> extends JpaRepository<T, ID> { }
3.這個介面中先什麼都不實現, 接著再寫一個它的實現
@NoRepositoryBean
@Transactional(readOnly = true)
public class SimpleBaseJpaRepository<T extends TableEntity, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements
BaseJpaRepository<T, ID> {
}
注意: 上面這兩個類頭上的@NoRepositoryBean註解標明這不是一個Repository的bean, 不需要spring 來自動實現Impl子類
4.擴充套件工廠類
僅僅有上面這個擴充套件介面和擴充套件實現是不能完成對Spring Data JPA 擴充套件的. 我們注意到上一章我們只是寫了一個 Repository的介面並繼承了JpaRepository, 並沒有寫實現類. 注入這個介面就可以使用它的方法來. 實際上Spring Data JPA依據繼承的JpaRepository, 用工廠自動填補了一個子類的. 那麼我們雖然擴充套件了JpaRepository, 但是沒有擴充套件Spring Data JPA的工廠方法,就還是完成擴充套件. 接下來我們開始擴充套件工廠類:
public class BaseJpaRepositoryFactory extends JpaRepositoryFactory {
public BaseJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return SimpleBaseJpaRepository.class;
}
}
注意: 這個類繼承了JpaRepositoryFactory, 也就是JpaRepository的工廠類, 並且覆蓋了getRepositoryBaseClass的方法,讓它返回我們擴充套件的SimpleBaseJpaRepository.
5.建立JpaRepositoryFactory的Factory
有了擴充套件工廠類後,還需要有一個建立JpaRepositoryFactory的Factory, 程式碼示例如下:
public class BaseJpaRepositoryFactoryBean<R extends JpaRepository<T, ID>, T extends TableEntity, ID extends Serializable>
extends JpaRepositoryFactoryBean<R, T, ID> {
public BaseJpaRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(
EntityManager entityManager) {
return new BaseJpaRepositoryFactory(entityManager);
}
}
6.建立RepositoryConfig
到這裡,對Spring Data JPA的擴充套件框架已經搭好了, 在給BaseJpaRepository和SimpleBaseJpaRepository填寫擴充套件方法之前,我們需要對上個例子中的RepositoryConfig進行一下修改. 找到RepositoryConfig這個類, 將@EnableJpaRepositories註解中的repositoryFactoryBeanClass修改為我們自己的工廠的工廠類 BaseJpaRepositoryFactoryBean.class 程式碼如下:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = {"org.learning.repository"},
repositoryFactoryBeanClass = BaseJpaRepositoryFactoryBean.class)
public class RepositoryConfig {
private static final String HIBERNATE_DIALECT = "hibernate.dialect";
private static final String HIBERNATE_SHOW_SQL = "hibernate.show.sql";
private static final String HIBERNATE_HBM2DDL_AUTO = "hibernate.hbm2ddl.auto";
private static final String HIBERNATE_EJB_NAMING_STRATEGY = "hibernate.ejb.naming_strategy";
@Autowired
private DataSource dataSource;
@Value("${spring.jpa.show-sql}")
private String showSql;
@Value("${spring.jpa.generate-ddl}")
private String generateDdl;
@Value("${spring.jpa.hibernate.ddl-auto}")
private String hibernateDdl;
@Value("${spring.jpa.database-platform}")
private String databasePlatform;
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setDataSource(dataSource);
factory.setPackagesToScan("org.learning.entity");
Map<String, Object> jpaProperties = new HashMap<>();
jpaProperties.put(HIBERNATE_SHOW_SQL, showSql);
jpaProperties.put(HIBERNATE_DIALECT, databasePlatform);
jpaProperties.put(HIBERNATE_HBM2DDL_AUTO, hibernateDdl);
jpaProperties.put(HIBERNATE_EJB_NAMING_STRATEGY, "org.hibernate.cfg.ImprovedNamingStrategy");
factory.setJpaPropertyMap(jpaProperties);
factory.afterPropertiesSet();
return factory;
}
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory().getObject());
return txManager;
}
}
7.開始填寫擴充套件方法, 首先需要給SimpleBaseJpaRepository建立一個建構函式,程式碼如下:
@NoRepositoryBean
@Transactional(readOnly = true)
public class SimpleBaseJpaRepository<T extends TableEntity, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements
BaseJpaRepository<T, ID> {
protected EntityManager entityManager;
private final JpaEntityInformation<T, ?> entityInformation;
protected Class<T> domainClass;
/**
* 父類的建構函式
*
* @param entityInformation
* @param entityManager
*/
public SimpleBaseJpaRepository(
JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
this.entityInformation = entityInformation;
this.domainClass = entityInformation.getJavaType();
}
}
在建構函式中需要呼叫父類的建構函式把EntityManager和JpaEntityInformation初始化了.
8.自定義一個動態查詢器
Spring Data JPA沒有提供類似Hibernate中Criteria的動態查詢器,但是往往專案中有很多動態查詢.那就封裝一個吧. 下面是程式碼示例:
Criterion介面:
public interface Criterion {
/**
* 操作符
*/
enum Operator {
EQ, NE, LIKE, GT, LT, GTE, LTE, AND, OR, ISNULL, ISNOTNULL, LEFTLIKE, RIGHTLIKE, BETWEEN, IN
}
Predicate toPredicate(Root<?> root, CriteriaQuery<?> query, CriteriaBuilder builder);
}
Criteria查詢器, 繼承Specification:
public class Criteria<T> implements Specification<T> {
private List<Criterion> criterions = new ArrayList<Criterion>();
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
if (!criterions.isEmpty()) {
List<Predicate> predicates = new ArrayList<Predicate>();
for(Criterion c : criterions){
predicates.add(c.toPredicate(root, query, builder));
}
// 將所有條件用 and 聯合起來
if (predicates.size() > 0) {
return builder.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
return builder.conjunction();
}
/**
* 增加簡單條件表示式
* @param criterion
*/
public void add(Criterion criterion){
if(criterion!=null){
criterions.add(criterion);
}
}
private boolean isField(Field[] fields, String queryKey) {
if (fields == null || fields.length == 0) {
return false;
}
for (Field field : fields) {
if (field.getName().equals(queryKey)) {
return true;
}
}
return false;
}
}
再建幾個表示式類
public class SimpleExpression implements Criterion {
private String fieldName; //屬性名
private Object value; //對應值
private Object[] values; //對應值
private Operator operator; //計算符
protected SimpleExpression(String fieldName, Object value, Operator operator) {
this.fieldName = fieldName;
this.value = value;
this.operator = operator;
}
protected SimpleExpression(String fieldName, Operator operator) {
this.fieldName = fieldName;
this.operator = operator;
}
protected SimpleExpression(String fieldName, Operator operator, Object... values) {
this.fieldName = fieldName;
this.values = values;
this.operator = operator;
}
public String getFieldName() {
return fieldName;
}
public Object getValue() {
return value;
}
public Operator getOperator() {
return operator;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
Path expression ;
if(fieldName.contains(".")){
String[] names = StringUtils.split(fieldName, ".");
expression = root.get(names[0]);
for (int i = 1; i < names.length; i++) {
expression = expression.get(names[i]);
}
}else{
expression = root.get(fieldName);
}
switch (operator) {
case EQ:
return builder.equal(expression, value);
case NE:
return builder.notEqual(expression, value);
case LIKE:
return builder.like((Expression<String>) expression, "%" + value + "%");
case LEFTLIKE:
return builder.like((Expression<String>) expression, "%" + value);
case RIGHTLIKE:
return builder.like((Expression<String>) expression, value + "%");
case LT:
return builder.lessThan(expression, (Comparable) value);
case GT:
return builder.greaterThan(expression, (Comparable) value);
case LTE:
return builder.lessThanOrEqualTo(expression, (Comparable) value);
case GTE:
return builder.greaterThanOrEqualTo(expression, (Comparable) value);
case ISNULL:
return builder.isNull(expression);
case ISNOTNULL:
return builder.isNotNull(expression);
case IN:
return ((CriteriaBuilderImpl)builder).in(expression, values);
default:
return null;
}
}
}
public class LogicalExpression implements Criterion {
private Criterion[] criterion; // 邏輯表示式中包含的表示式
private Operator operator; //計算符
LogicalExpression(Criterion[] criterions, Operator operator) {
this.criterion = criterions;
this.operator = operator;
}
@Override
public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<Predicate>();
for (Criterion aCriterion : this.criterion) {
predicates.add(aCriterion.toPredicate(root, query, builder));
}
switch (operator) {
case OR:
return builder.or(predicates.toArray(new Predicate[predicates.size()]));
default:
return builder.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
}
public class BetweenExpression implements Criterion {
private final String fieldName;
private Object lo;
private Object hi;
BetweenExpression(String fieldName, Object lo, Object hi) {
this.fieldName = fieldName;
this.lo = lo;
this.hi = hi;
}
@Override
public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Path expression ;
if(fieldName.contains(".")){
String[] names = StringUtils.split(fieldName, ".");
expression = root.get(names[0]);
for (int i = 1; i < names.length; i++) {
expression = expression.get(names[i]);
}
}else{
expression = root.get(fieldName);
}
if (lo instanceof Date && hi instanceof Date) {
return builder.between(expression, (Date)lo, (Date)hi);
} else if (lo instanceof String && hi instanceof String) {
return builder.between(expression, (String)lo, (String)hi);
} else if (lo instanceof Integer && hi instanceof Integer) {
return builder.between(expression, (Integer)lo, (Integer)hi);
} else if (lo instanceof Double && hi instanceof Double) {
return builder.between(expression, (Double)lo, (Double)hi);
} else if (lo instanceof BigDecimal && hi instanceof BigDecimal) {
return builder.between(expression, (BigDecimal)lo, (BigDecimal)hi);
} else {
return null;
}
}
}
public class ColumnExpression implements Criterion {
private final String fieldNameA;
private final String fieldNameB;
private Operator operator; //計算符
ColumnExpression(String fieldNameA, String fieldNameB, Operator operator) {
this.fieldNameA = fieldNameA;
this.fieldNameB = fieldNameB;
this.operator = operator;
}
@Override
public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Path expressionA ;
if(fieldNameA.contains(".")){
String[] names = StringUtils.split(fieldNameA, ".");
expressionA = root.get(names[0]);
for (int i = 1; i < names.length; i++) {
expressionA = expressionA.get(names[i]);
}
}else{
expressionA = root.get(fieldNameA);
}
Path expressionB ;
if(fieldNameB.contains(".")){
String[] names = StringUtils.split(fieldNameB, ".");
expressionB = root.get(names[0]);
for (int i = 1; i < names.length; i++) {
expressionB = expressionB.get(names[i]);
}
}else{
expressionB = root.get(fieldNameB);
}
switch (operator) {
case EQ:
return builder.equal(expressionA, expressionB);
case NE:
return builder.notEqual(expressionA, expressionB);
default:
return null;
}
}
}
最後是條件比較器Restrictions:
public class Restrictions {
/**
* 等於
* @param fieldName
* @param value
* @return
*/
public static SimpleExpression eq(String fieldName, Object value) {
if(value == null) {
return null;
}
return new SimpleExpression (fieldName, value, Criterion.Operator.EQ);
}
/**
* 不等於
* @param fieldName
* @param value
* @return
*/
public static SimpleExpression ne(String fieldName, Object value) {
if(value == null) {
return null;
}
return new SimpleExpression (fieldName, value, Criterion.Operator.NE);
}
/**
* 模糊匹配
* @param fieldName
* @param value
* @return
*/
public static SimpleExpression like(String fieldName, String value) {
if(value == null) {
return null;
}
return new SimpleExpression (fieldName, value, Criterion.Operator.LIKE);
}
/**
* 模糊匹配
* @param fieldName
* @param value
* @return
*/
public static SimpleExpression leftLike(String fieldName, String value) {
if(value == null) {
return null;
}
return new SimpleExpression (fieldName, value, Criterion.Operator.LEFTLIKE);
}
/**
* 模糊匹配
* @param fieldName
* @param value
* @return
*/
public static SimpleExpression rightLike(String fieldName, String value) {
if(value == null) {
return null;
}
return new SimpleExpression (fieldName, value, Criterion.Operator.RIGHTLIKE);
}
/**
* 大於
* @param fieldName
* @param value
* @return
*/
public static SimpleExpression gt(String fieldName, Object value) {
if(value == null) {
return null;
}
return new SimpleExpression (fieldName, value, Criterion.Operator.GT);
}
/**
* 小於
* @param fieldName
* @param value
* @return
*/
public static SimpleExpression lt(String fieldName, Object value) {
if(value == null) {
return null;
}
return new SimpleExpression (fieldName, value, Criterion.Operator.LT);
}
/**
* 大於等於
* @param fieldName
* @param value
* @return
*/
public static SimpleExpression gte(String fieldName, Object value) {
if(value == null) {
return null;
}
return new SimpleExpression (fieldName, value, Criterion.Operator.GTE);
}
/**
* 小於等於
* @param fieldName
* @param value
* @return
*/
public static SimpleExpression lte(String fieldName, Object value) {
if(value == null) {
return null;
}
return new SimpleExpression (fieldName, value, Criterion.Operator.LTE);
}
/**
* 並且
* @param criterions
* @return
*/
public static LogicalExpression and(Criterion... criterions){
return new LogicalExpression(criterions, Criterion.Operator.AND);
}
/**
* 或者
* @param criterions
* @return
*/
public static LogicalExpression or(Criterion... criterions){
return new LogicalExpression(criterions, Criterion.Operator.OR);
}
/**
* 包含於
* @param fieldName
* @param value
* @return
*/
@SuppressWarnings("rawtypes")
public static SimpleExpression in(String fieldName, Collection value) {
return new SimpleExpression(fieldName, Criterion.Operator.IN, value.toArray());
}
/**
* 不包含於
* @param fieldName
* @param value
* @return
*/
@SuppressWarnings("rawtypes")
public static LogicalExpression notIn(String fieldName, Collection value) {
if((value==null||value.isEmpty())){
return null;
}
SimpleExpression[] ses = new SimpleExpression[value.size()];
int i=0;
for(Object obj : value){
ses[i]=new SimpleExpression(fieldName,obj, Criterion.Operator.NE);
i++;
}
return new LogicalExpression(ses, Criterion.Operator.AND);
}
/**
* 列為空
* @param fieldName
* @return
*/
public static LogicalExpression isNull(String fieldName) {
SimpleExpression[] ses = new SimpleExpression[1];
ses[0] = new SimpleExpression(fieldName, Criterion.Operator.ISNULL);
return new LogicalExpression(ses, Criterion.Operator.ISNULL);
}
/**
* 列不為空
* @param fieldName
* @return
*/
public static LogicalExpression isNotNull(String fieldName) {
SimpleExpression[] ses = new SimpleExpression[1];
ses[0] = new SimpleExpression(fieldName, Criterion.Operator.ISNOTNULL);
return new LogicalExpression(ses, Criterion.Operator.ISNOTNULL);
}
/**
* 時間範圍
* @param fieldName
* @return
*/
public static LogicalExpression between(String fieldName, Object startDate, Object endDate) {
BetweenExpression[] bes = new BetweenExpression[1];
bes[0] = new BetweenExpression(fieldName, startDate, endDate);
return new LogicalExpression(bes, Criterion.Operator.BETWEEN);
}
/**
* 等於
* @param fieldNameA
* @param fieldNameB
* @return
*/
public static ColumnExpression eqCol(String fieldNameA, String fieldNameB) {
if(StringUtil.isBlank(fieldNameA) || StringUtil.isBlank(fieldNameB)) {
return null;
}
return new ColumnExpression(fieldNameA, fieldNameB, Criterion.Operator.EQ);
}
/**
* bu等於
* @param fieldNameA
* @param fieldNameB
* @return
*/
public static ColumnExpression neCol(String fieldNameA, String fieldNameB) {
if(StringUtil.isBlank(fieldNameA) || StringUtil.isBlank(fieldNameB)) {
return null;
}
return new ColumnExpression(fieldNameA, fieldNameB, Criterion.Operator.NE);
}
/**
* 時間範圍
* @param fieldName
* @return
*/
public static LogicalExpression eqDate(String fieldName, Object date) {
BetweenExpression[] bes = new BetweenExpression[1];
Date startDate;
Date endDate;
try {
if(date instanceof String){
startDate = stringToDateTime(date.toString() + " 00:00:00");
endDate = stringToDateTime(date.toString() + " 23:59:59");
}else if(date instanceof Date){
Calendar calendar = Calendar.getInstance();
calendar.setTime((Date)date);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND,0);
startDate = calendar.getTime();
calendar.set(Calendar.HOUR_OF_DAY,23);
calendar.set(Calendar.MINUTE,59);
calendar.set(Calendar.SECOND,59);
endDate = calendar.getTime();
}else{
return null;
}
}catch (Exception ignored){
return null;
}
bes[0] = new BetweenExpression(fieldName, startDate, endDate);
return new LogicalExpression(bes, Criterion.Operator.BETWEEN);
}
private final static String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
private static Date stringToDateTime(String dateString) {
if (dateString == null) {
return null;
}
try {
DateFormat df = new SimpleDateFormat(DATETIME_PATTERN);
df.setLenient(false);
return df.parse(dateString);
} catch (ParseException e) {
return null;
}
}
}
本章結束
本章介紹了jpa封裝的部分程式碼, 下一章接著展示後面的程式碼