1. 程式人生 > >Spring Boot 實踐 第六章 Spring data JAP在實際開發中的封裝和應用(上)

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封裝的部分程式碼, 下一章接著展示後面的程式碼