Spring Boot整理——Spring Data JPA

一、基本介紹

        JPA誕生的緣由是爲了整合第三方ORM框架,建立一種標準的方式,百度百科說是JDK爲了實現ORM的天下歸一,目前也是在按照這個方向發展,但是還沒能完全實現。在ORM框架中,Hibernate是一支很大的部隊,使用很廣泛,也很方便,能力也很強,同時Hibernate也是和JPA整合的比較良好,我們可以認爲JPA是標準,事實上也是,JPA幾乎都是接口,實現都是Hibernate在做,宏觀上面看,在JPA的統一之下Hibernate很良好的運行。

  我們都知道,在使用持久化工具的時候,一般都有一個對象來操作數據庫,在原生的Hibernate中叫做Session,在JPA中叫做EntityManager,在MyBatis中叫做SqlSession,通過這個對象來操作數據庫。我們一般按照三層結構來看的話,Service層做業務邏輯處理,Dao層和數據庫打交道,在Dao中,就存在着上面的對象。那麼ORM框架本身提供的功能有什麼呢?答案是基本的CRUD,所有的基礎CRUD框架都提供,我們使用起來感覺很方便,很給力,業務邏輯層面的處理ORM是沒有提供的,如果使用原生的框架,業務邏輯代碼我們一般會自定義,會自己去寫SQL語句,然後執行。在這個時候,Spring-data-jpa的威力就體現出來了,ORM提供的能力他都提供,ORM框架沒有提供的業務邏輯功能Spring-data-jpa也提供,全方位的解決用戶的需求。使用Spring-data-jpa進行開發的過程中,常用的功能,我們幾乎不需要寫一條sql語句。

二、環境配置

1、maven配置

        首先需要spring相關架包,其實spring-data-jpa裏面已經依賴了,如果你想用自己的版本則需要額外引入spring相關包.JPA實現還都是hibernate去實現的,所以還需要hibernate相關包.mysql就更不用說了.

    <!--JPA start-->
      <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.10.4.RELEASE</version>
      </dependency>
    <!--JPA end-->
​
    <!--hibernate start-->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>${hibernate.version}</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>${hibernate.version}</version>
    </dependency>
    <!--hibernate end-->
​
    <!--mysql start-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>${mysql.version}</version>
    </dependency>
    <!--mysql end-->

2、整合Spring

整合Spring主要有以下幾點要注意:

  • 數據源配置  
  • JPA提供者,JPA屬性配置  
  • 事務配置  
  • jpa:repositories 配置

具體如下代碼:

 <!-- 加載數據庫配置文件 -->
    <context:property-placeholder location="classpath:config.properties"/>
<!--配置數據庫連接池Druid-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 數據庫基本信息配置 -->
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="filters" value="${filters}" />
        <!-- 最大併發連接數 -->
        <property name="maxActive" value="${maxActive}" />
        <!-- 初始化連接數量 -->
        <property name="initialSize" value="${initialSize}" />
        <!-- 配置獲取連接等待超時的時間 -->
        <property name="maxWait" value="${maxWait}" />
        <!-- 最小空閒連接數 -->
        <property name="minIdle" value="${minIdle}" />
        <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
        <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
        <property name="validationQuery" value="SELECT 1" />
        <property name="testWhileIdle" value="${testWhileIdle}" />
        <property name="testOnBorrow" value="${testOnBorrow}" />
        <property name="testOnReturn" value="${testOnReturn}" />
        <property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}" />
        <!-- 打開removeAbandoned功能 -->
        <property name="removeAbandoned" value="${removeAbandoned}" />
        <!-- 1800秒,也就是30分鐘 -->
        <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />
        <!-- 關閉abanded連接時輸出錯誤日誌 -->
        <property name="logAbandoned" value="${logAbandoned}" />
    </bean>
​
    <!--第二步-->
    <!--定義實體的工廠bean-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--實體類位置-->
        <property name="packagesToScan" value="cn.mrdear.entity"/>
        <!--持久化單元名-->
        <property name="persistenceUnitName" value="TestJPA" />
        <!--JPA提供者-->
        <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
        <!--JPA屬性-->
        <property name="jpaProperties">
            <props>
                <!--配置方言-->
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <!--激活查詢日誌功能-->
                <prop key="hibernate.show_sql">false</prop>
                <!--優雅地輸出Sql-->
                <prop key="hibernate.format_sql">false</prop>
                <!--添加一條解釋型標註-->
                <prop key="hibernate.use_sql_comments">false</prop>
                <!--配置如何根據java模型生成數據庫表結構,常用update,validate-->
                <prop key="hibernate.hbm2ddl.auto">none</prop>
            </props>
        </property>
    </bean>
​
    <!--第三步-->
    <!--定義事務管理器-->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="dataSource" ref="dataSource"/>
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
​
    <!--第四步-->
    <!--定義repository接口的存放目錄-->
    <!--定義接口實現的後綴,通常用Impl-->
    <!--定義實體工廠的引用-->
    <!--定義事務管理器的引用-->
    <jpa:repositories base-package="cn.mrdear.repository"
                      repository-impl-postfix="Impl"
                      entity-manager-factory-ref="entityManagerFactory"
                      transaction-manager-ref="transactionManager"/>
​
    <!--第五步-->
    <!--聲明採用註解的方式申明事務-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

3、創建實體類

實體類中常用註解:  

  •  @Entity :聲明這個類是一個實體類  
  •  @Table:指定映射到數據庫的表格  
  •  @Id :映射到數據庫表的主鍵屬性,一個實體只能有一個屬性被映射爲主鍵  
  •  @GeneratedValue:主鍵的生成策略  
  •  @Column配置單列屬性 
@Entity//標識該爲一個實體
@Table(name = "user")//關聯數據庫中的user表
public class User {
    @Id//標識該屬性爲主鍵
    private Integer id;
    private String name;
    private String address;
    private String phone;
    //省略get和set
}

4、Repository接口

  • Repository: 最頂層的接口,是一個空接口,目的是爲了統一所有的Repository的類型,且能讓組件掃描時自動識別 
  • CrudRepository: Repository的子接口,提供CRUD 的功能。 
  • PagingAndSortingRepository:CrudRepository的子接口, 添加分頁排序。 
  • JpaRepository: PagingAndSortingRepository的子接口,增加批量操作等。 
  • JpaSpecificationExecutor: 用來做複雜查詢的接口。

由圖來看,一般使用JpaRepository這個接口做查詢即可.這個接口擁有如下方法:

  • delete刪除或批量刪除 
  • findOne查找單個 
  • findAll查找所有 
  • save保存單個或批量保存 
  • saveAndFlush保存並刷新到數據庫

知道這些我們就可以創建repository 了:

//User表示該Repository與實體User關聯,主鍵類型爲Integer
public interface UserRepository extends JpaRepository<User,Integer> {
}

​這樣就完成了一個基本Repository的創建,可以直接使用其中的方法,而不需要去寫實現類。

5、測試

        關於測試這裏,我把測試案例寫到test文件夾的話,總是報實體類未被JPA管理,所以改寫到java文件夾,具體原因未知.

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
        UserRepository userRepository = (UserRepository) applicationContext.getBean("userRepository");
        System.out.println(userRepository.findAll());
        System.out.println(userRepository.findOne(1));
        System.out.println(userRepository.findAll(new Sort(new Sort.Order(Sort.Direction.ASC,"id"))));
    }

三、CRUD操作

1、增加

        增加可以使用JpaRepository接口裏面的save方法.查看源碼可以發現實際上是使用了em.persist(entity)來使對象進入持久化狀態,最後提交事務的時候再一起更新到數據庫:

User user = new User();
user.setId(99);
user.setAddress("上海");
user.setName("張三");
user.setPhone("110");
//保存單個
userRepository.save(user);
//保存或更新
userRepository.saveAndFlush(user);
List<User> users = new ArrayList<>();
users.add(user);
//保存多個
userRepository.save(users);

        這裏還可以批量插入,對於mysql支持INSERT user VALUES (20,'王二','111','111'),(21,'王二','111','111');類似這樣的sql語句,具體實現就需要自己去寫實現了,這樣可以一次插入多條記錄,效率很高.至於一次插入多少條就可以根據你的業務量來自己制定。

2、刪除

        刪除都是根據主鍵來刪除的,區別就是多條sql和單條sql :

User user = new User();
user.setId(21);
user.setName("王二");
/**
 * 刪除都是根據主鍵刪除
 */
//刪除單條,根據主鍵值
userRepository.delete(20);
//刪除全部,先findALL查找出來,再一條一條刪除,最後提交事務
userRepository.deleteAll();
//刪除全部,一條sql
userRepository.deleteAllInBatch();
List<User> users = new ArrayList<>();
users.add(user);
//刪除集合,一條一條刪除
userRepository.delete(users);
//刪除集合,一條sql,拼接or語句 如 id=1 or id=2
userRepository.deleteInBatch(users);

3、修改

        修改也是根據主鍵來更新的 :

User user = new User();
user.setId(1);
user.setName("張三");
/**
 * 更新也是根據主鍵來更新 update XX xx where id=1
 */
userRepository.saveAndFlush(user);

批量更新的話,就調用entityManager的merge函數來更新. 

//首先在service層獲取持久化管理器:
@PersistenceContext
private EntityManager em;
//批量更新方法,同理插入,刪除也都可以如此做.
@Transactional
public void batchUpateCustom(List<User> users) {
    // TODO Auto-generated method stub
    for(int i = 0; i < users.size(); i++) { 
        em.merge(users.get(i)); 
        if(i % 30== 0) {
            em.flush(); 
            em.clear(); 
        } 
    }
}

4、簡單查詢

        單表查詢,大部分都可以使用下面的方法解決,多表聯合查詢的話,下面方法就不是很實用,下一節分析多表查詢。

1.使用JpaRepository方法

//查找全部
userRepository.findAll();
//分頁查詢全部,返回封裝了分頁信息
Page<User> pageInfo = userRepository.findAll(new PageRequest(1, 3, Sort.Direction.ASC,"id"));
//查找全部,並排序
userRepository.findAll(new Sort(new Sort.Order(Sort.Direction.ASC,"id")));
User user = new User();
user.setName("小紅");
//條件查詢,可以聯合分頁,排序
userRepository.findAll(Example.of(user));
//查詢單個
userRepository.findOne(1);

2.解析方法名創建查詢

        規則:find+全局修飾+By+實體的屬性名稱+限定詞+連接詞+ …(其它實體屬性)+OrderBy+排序屬性+排序方向。例如:

//分頁查詢出符合姓名的記錄,同理Sort也可以直接加上
public List<User> findByName(String name, Pageable pageable);

全局修飾: Distinct, Top, First

關鍵詞: IsNull, IsNotNull, Like, NotLike, Containing, In, NotIn,

IgnoreCase, Between, Equals, LessThan, GreaterThan, After, Before…

排序方向: Asc, Desc

連接詞: And, Or

And — 等價於 SQL 中的 and 關鍵字,比如 findByUsernameAndPassword(String user, Striang pwd);

Or — 等價於 SQL 中的 or 關鍵字,比如 findByUsernameOrAddress(String user, String addr);

Between — 等價於 SQL 中的 between 關鍵字,比如 findBySalaryBetween(int max, int min);

LessThan — 等價於 SQL 中的 “<”,比如 findBySalaryLessThan(int max);

GreaterThan — 等價於 SQL 中的”>”,比如 findBySalaryGreaterThan(int min);

IsNull — 等價於 SQL 中的 “is null”,比如 findByUsernameIsNull();

IsNotNull — 等價於 SQL 中的 “is not null”,比如 findByUsernameIsNotNull();

NotNull — 與 IsNotNull 等價;

Like — 等價於 SQL 中的 “like”,比如 findByUsernameLike(String user);

NotLike — 等價於 SQL 中的 “not like”,比如 findByUsernameNotLike(String user);

OrderBy — 等價於 SQL 中的 “order by”,比如 findByUsernameOrderBySalaryAsc(String user);

Not — 等價於 SQL 中的 “! =”,比如 findByUsernameNot(String user);

In — 等價於 SQL 中的 “in”,比如 findByUsernameIn(Collection userList) ,方法的參數可以是 Collection 類型,也可以是數組或者不定長參數;

NotIn — 等價於 SQL 中的 “not in”,比如 findByUsernameNotIn(Collection userList) ,方法的參數可以是 Collection 類型,也可以是數組或者不定長參數;

嵌套實體:

主實體中子實體的名稱+ _ +子實體的屬性名稱

List findByAddress_ZipCode(ZipCode zipCode)

表示查詢所有 Address(地址)的zipCode(郵編)爲指定值的所有Person(人員)

3.JPQL查詢

        一個類似HQL的語法,在接口上使用@Query標識:

 @Query("select a from user a where a.id = ?1")
 public User findById(Long id);

使用@Modifying標識修改

 @Modifying
 @Query("update User a set a.name = ?1 where a.id < ?2")
 public int updateName(String name, Long id);

攜帶分頁信息:

@Query("select u from User u where u.name=?1")
public List<User> findByName(String name, Pageable pageable);

        除此之外也可以使用原生sql,只需要@Query(nativeQuery=true)標識即可.

 

創建查詢順序:

Spring Data JPA 在爲接口創建代理對象時,如果發現同時存在多種上述情況可用,它該優先採用哪種策略呢?爲此, 提供了 query-lookup-strategy 屬性,用以指定查找的順序。它有如下三個取值:

  1. create — 通過解析方法名字來創建查詢。即使有符合的命名查詢,或者方法通過 @Query 指定的查詢語句,都將會被忽略。
  2. create-if-not-found — 如果方法通過 @Query 指定了查詢語句,則使用該語句實現查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則通過解析方法名字來創建查詢。這是 query-lookup-strategy 屬性的默認值。
  3. use-declared-query — 如果方法通過 @Query 指定了查詢語句,則使用該語句實現查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則拋出異常。

4.計數

        計數就直接使用JpaRepository的count方法:

//查找總數量
userRepository.count();
User user = new User();
user.setName("小紅");
//條件計數
userRepository.count(Example.of(user));

5.判斷是否存在

        計數就直接使用JpaRepository的exists方法:

//根據主鍵判斷是否存在
userRepository.exists(1);
User user = new User();
user.setName("小紅");
//根據條件判斷是否存在
userRepository.exists(Example.of(user));

6.自定義查詢

        首先自定義一個接口,用於定義自定義方法,如UserRepositoryCustom,然後讓UserRepository實現該接口,這樣的話就可以使用其中的方法。然後寫UserRepositoryImpl實現UserRepositoryCustom接口。

        最後設置jpa:repositories的repository-impl-postfix="Impl",這樣的話JPA會查找自定義實現類命名規則,這樣的話JPA在相應UserRepository包下面查找實現類,找到則會使用其中的實現方法,而不去自己實現。具體可以看項目demo,或者下一節的複雜查詢。

5、複雜查詢

1.使用CriteriaBuilder構建JPQL

        在UserRepositoryImpl中使用CriteriaBuilder實現根據id查詢,下面是代碼:

public void findById(Integer id){
    //select u from User u where u.id = 1
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<User> cq = cb.createQuery(User.class);
    Root<User> root = cq.from(User.class); //from User
    cq.select(root); //select * from User
    javax.persistence.criteria.Predicate pre = cb.equal(root.get("id").as(Integer.class),id);//id=1
    cq.where(pre);//where id=1
    Query query = entityManager.createQuery(cq);//select u from User u where u.id = 1
​
    System.out.println(query.getResultList());
}

缺點:

  • 代碼量多
  •  不易維護
  •  條件複雜的話,則會顯得很混亂.

2.使用JpaSpecificationExecutor查詢

        該接口有如下方法,裏面傳入條件都是Specification,該接口會返回一個Predicate條件集合,因此就可以在這裏封裝:

public interface JpaSpecificationExecutor<T> {
    T findOne(Specification<T> spec);
    List<T> findAll(Specification<T> spec);
    Page<T> findAll(Specification<T> spec, Pageable pageable);
    List<T> findAll(Specification<T> spec, Sort sort);
    long count(Specification<T> spec);
}

1.構造過濾條件集合

        Operator枚舉類裏面的operator屬性爲了構建原生sql使用:

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
​
import java.io.Serializable;
​
/**
 * 篩選
 */
public class Filter implements Serializable {
​
    private static final long serialVersionUID = -8712382358441065075L;
​
    /**
     * 運算符
     */
    public enum Operator {
​
        /** 等於 */
        eq(" = "),
​
        /** 不等於 */
        ne(" != "),
​
        /** 大於 */
        gt(" > "),
​
        /** 小於 */
        lt(" < "),
​
        /** 大於等於 */
        ge(" >= "),
​
        /** 小於等於 */
        le(" <= "),
​
        /** 類似 */
        like(" like "),
​
        /** 包含 */
        in(" in "),
​
        /** 爲Null */
        isNull(" is NULL "),
​
        /** 不爲Null */
        isNotNull(" is not NULL ");
        Operator(String operator) {
            this.operator = operator;
        }
​
        private String operator;
​
        public String getOperator() {
            return operator;
        }
​
        public void setOperator(String operator) {
            this.operator = operator;
        }
    }
​
    /** 默認是否忽略大小寫 */
    private static final boolean DEFAULT_IGNORE_CASE = false;
​
    /** 屬性 */
    private String property;
​
    /** 運算符 */
    private Filter.Operator operator;
​
    /** 值 */
    private Object value;
​
    /** 是否忽略大小寫 */
    private Boolean ignoreCase = DEFAULT_IGNORE_CASE;
​
    /**
     * 構造方法
     */
    public Filter() {
    }
​
    /**
     * 構造方法
     *
     * @param property
     *            屬性
     * @param operator
     *            運算符
     * @param value
     *            值
     */
    public Filter(String property, Filter.Operator operator, Object value) {
        this.property = property;
        this.operator = operator;
        this.value = value;
    }
​
    /**
     * 構造方法
     *
     * @param property
     *            屬性
     * @param operator
     *            運算符
     * @param value
     *            值
     * @param ignoreCase
     *            忽略大小寫
     */
    public Filter(String property, Filter.Operator operator, Object value, boolean ignoreCase) {
        this.property = property;
        this.operator = operator;
        this.value = value;
        this.ignoreCase = ignoreCase;
    }
​
    /**
     * 返回等於篩選
     *
     * @param property
     *            屬性
     * @param value
     *            值
     * @return 等於篩選
     */
    public static Filter eq(String property, Object value) {
        return new Filter(property, Filter.Operator.eq, value);
    }
​
    /**
     * 返回等於篩選
     *
     * @param property
     *            屬性
     * @param value
     *            值
     * @param ignoreCase
     *            忽略大小寫
     * @return 等於篩選
     */
    public static Filter eq(String property, Object value, boolean ignoreCase) {
        return new Filter(property, Filter.Operator.eq, value, ignoreCase);
    }
​
    /**
     * 返回不等於篩選
     *
     * @param property
     *            屬性
     * @param value
     *            值
     * @return 不等於篩選
     */
    public static Filter ne(String property, Object value) {
        return new Filter(property, Filter.Operator.ne, value);
    }
​
    /**
     * 返回不等於篩選
     *
     * @param property
     *            屬性
     * @param value
     *            值
     * @param ignoreCase
     *            忽略大小寫
     * @return 不等於篩選
     */
    public static Filter ne(String property, Object value, boolean ignoreCase) {
        return new Filter(property, Filter.Operator.ne, value, ignoreCase);
    }
​
    /**
     * 返回大於篩選
     *
     * @param property
     *            屬性
     * @param value
     *            值
     * @return 大於篩選
     */
    public static Filter gt(String property, Object value) {
        return new Filter(property, Filter.Operator.gt, value);
    }
​
    /**
     * 返回小於篩選
     *
     * @param property
     *            屬性
     * @param value
     *            值
     * @return 小於篩選
     */
    public static Filter lt(String property, Object value) {
        return new Filter(property, Filter.Operator.lt, value);
    }
​
    /**
     * 返回大於等於篩選
     *
     * @param property
     *            屬性
     * @param value
     *            值
     * @return 大於等於篩選
     */
    public static Filter ge(String property, Object value) {
        return new Filter(property, Filter.Operator.ge, value);
    }
​
    /**
     * 返回小於等於篩選
     *
     * @param property
     *            屬性
     * @param value
     *            值
     * @return 小於等於篩選
     */
    public static Filter le(String property, Object value) {
        return new Filter(property, Filter.Operator.le, value);
    }
​
    /**
     * 返回相似篩選
     *
     * @param property
     *            屬性
     * @param value
     *            值
     * @return 相似篩選
     */
    public static Filter like(String property, Object value) {
        return new Filter(property, Filter.Operator.like, value);
    }
​
    /**
     * 返回包含篩選
     *
     * @param property
     *            屬性
     * @param value
     *            值
     * @return 包含篩選
     */
    public static Filter in(String property, Object value) {
        return new Filter(property, Filter.Operator.in, value);
    }
​
    /**
     * 返回爲Null篩選
     *
     * @param property
     *            屬性
     * @return 爲Null篩選
     */
    public static Filter isNull(String property) {
        return new Filter(property, Filter.Operator.isNull, null);
    }
​
    /**
     * 返回不爲Null篩選
     *
     * @param property
     *            屬性
     * @return 不爲Null篩選
     */
    public static Filter isNotNull(String property) {
        return new Filter(property, Filter.Operator.isNotNull, null);
    }
​
    /**
     * 返回忽略大小寫篩選
     *
     * @return 忽略大小寫篩選
     */
    public Filter ignoreCase() {
        this.ignoreCase = true;
        return this;
    }
​
    /**
     * 獲取屬性
     *
     * @return 屬性
     */
    public String getProperty() {
        return property;
    }
​
    /**
     * 設置屬性
     *
     * @param property
     *            屬性
     */
    public void setProperty(String property) {
        this.property = property;
    }
​
    /**
     * 獲取運算符
     *
     * @return 運算符
     */
    public Filter.Operator getOperator() {
        return operator;
    }
​
    /**
     * 設置運算符
     *
     * @param operator
     *            運算符
     */
    public void setOperator(Filter.Operator operator) {
        this.operator = operator;
    }
​
    /**
     * 獲取值
     *
     * @return 值
     */
    public Object getValue() {
        return value;
    }
​
    /**
     * 設置值
     *
     * @param value
     *            值
     */
    public void setValue(Object value) {
        this.value = value;
    }
​
    /**
     * 獲取是否忽略大小寫
     *
     * @return 是否忽略大小寫
     */
    public Boolean getIgnoreCase() {
        return ignoreCase;
    }
​
    /**
     * 設置是否忽略大小寫
     *
     * @param ignoreCase
     *            是否忽略大小寫
     */
    public void setIgnoreCase(Boolean ignoreCase) {
        this.ignoreCase = ignoreCase;
    }
​
    /**
     * 重寫equals方法
     *
     * @param obj
     *            對象
     * @return 是否相等
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        Filter other = (Filter) obj;
        return new EqualsBuilder().append(getProperty(), other.getProperty()).append(getOperator(), other.getOperator()).append(getValue(), other.getValue()).isEquals();
    }
​
    /**
     * 重寫hashCode方法
     *
     * @return HashCode
     */
    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37).append(getProperty()).append(getOperator()).append(getValue()).toHashCode();
    }
}

2.構造排序字段

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.io.Serializable;
/**
 * 排序
 *
 * @author copy from shopxx
 */
public class Order implements Serializable {
​
    private static final long serialVersionUID = -3078342809727773232L;
​
    /**
     * 方向
     */
    public enum Direction {
​
        /** 遞增 */
        asc,
​
        /** 遞減 */
        desc
    }
​
    /** 默認方向 */
    private static final Order.Direction DEFAULT_DIRECTION = Order.Direction.desc;
​
    /** 屬性 */
    private String property;
​
    /** 方向 */
    private Order.Direction direction = DEFAULT_DIRECTION;
​
    @Override
    public String toString() {
        return property+" " + direction.name();
    }
​
    /**
     * 構造方法
     */
    public Order() {
    }
​
    /**
     * 構造方法
     *
     * @param property
     *            屬性
     * @param direction
     *            方向
     */
    public Order(String property, Order.Direction direction) {
        this.property = property;
        this.direction = direction;
    }
​
    /**
     * 返回遞增排序
     *
     * @param property
     *            屬性
     * @return 遞增排序
     */
    public static Order asc(String property) {
        return new Order(property, Order.Direction.asc);
    }
​
    /**
     * 返回遞減排序
     *
     * @param property
     *            屬性
     * @return 遞減排序
     */
    public static Order desc(String property) {
        return new Order(property, Order.Direction.desc);
    }
​
    /**
     * 獲取屬性
     *
     * @return 屬性
     */
    public String getProperty() {
        return property;
    }
​
    /**
     * 設置屬性
     *
     * @param property
     *            屬性
     */
    public void setProperty(String property) {
        this.property = property;
    }
​
    /**
     * 獲取方向
     *
     * @return 方向
     */
    public Order.Direction getDirection() {
        return direction;
    }
​
    /**
     * 設置方向
     *
     * @param direction
     *            方向
     */
    public void setDirection(Order.Direction direction) {
        this.direction = direction;
    }
​
    /**
     * 重寫equals方法
     *
     * @param obj
     *            對象
     * @return 是否相等
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        Order other = (Order) obj;
        return new EqualsBuilder().append(getProperty(), other.getProperty()).append(getDirection(), other.getDirection()).isEquals();
    }
​
    /**
     * 重寫hashCode方法
     *
     * @return HashCode
     */
    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37).append(getProperty()).append(getDirection()).toHashCode();
    }
}

3.查詢語句生成

1.基本框架

/**
 * 封裝查詢條件的實體
 */
public class QueryParams<T> implements Specification<T> {
​
    /** 屬性分隔符 */
    private static final String PROPERTY_SEPARATOR = ".";
    /**
     * and條件
     */
    private List<Filter> andFilters = new ArrayList<>();
    /**
     * or條件
     */
    private List<Filter> orFilters = new ArrayList<>();
    /**
     * 排序屬性
     */
    private List<Order> orders = new ArrayList<>();
        /**
     * 獲取Path
     *
     * @param path
     *            Path
     * @param propertyPath
     *            屬性路徑
     * @return Path
     */
    @SuppressWarnings("unchecked")
    private <X> Path<X> getPath(Path<?> path, String propertyPath) {
        if (path == null || StringUtils.isEmpty(propertyPath)) {
            return (Path<X>) path;
        }
        String property = StringUtils.substringBefore(propertyPath, PROPERTY_SEPARATOR);
        return getPath(path.get(property), StringUtils.substringAfter(propertyPath, PROPERTY_SEPARATOR));
    }
}

2.分析and條件

/**
 * 轉換爲Predicate
 */
@SuppressWarnings("unchecked")
private Predicate toAndPredicate(Root<T> root,CriteriaBuilder criteriaBuilder) {
    Predicate restrictions = criteriaBuilder.conjunction();
    if (root == null || CollectionUtils.isEmpty(andFilters)) {
        return restrictions;
    }
    for (Filter filter : andFilters) {
        if (filter == null) {
            continue;
        }
        String property = filter.getProperty();
        Filter.Operator operator = filter.getOperator();
        Object value = filter.getValue();
        Boolean ignoreCase = filter.getIgnoreCase();
        Path<?> path = getPath(root, property);
        if (path == null) {
            continue;
        }
        //根據運算符生成相應條件
        switch (operator) {
            case eq:
                if (value != null) {
                    if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(path, value));
                    }
                } else {
                    restrictions = criteriaBuilder.and(restrictions, path.isNull());
                }
                break;
            case ne:
                if (value != null) {
                    if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(path, value));
                    }
                } else {
                    restrictions = criteriaBuilder.and(restrictions, path.isNotNull());
                }
                break;
            case gt:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.gt((Path<Number>) path, (Number) value));
                }
                break;
            case lt:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) value));
                }
                break;
            case ge:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) value));
                }
                break;
            case le:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.le((Path<Number>) path, (Number) value));
                }
                break;
            case like:
                if (String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                    if (BooleanUtils.isTrue(ignoreCase)) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like((Path<String>) path, (String) value));
                    }
                }
                break;
            case in:
                restrictions = criteriaBuilder.and(restrictions, path.in(value));
                break;
            case isNull:
                restrictions = criteriaBuilder.and(restrictions, path.isNull());
                break;
            case isNotNull:
                restrictions = criteriaBuilder.and(restrictions, path.isNotNull());
                break;
        }
    }
    return restrictions;
}

3.分析or條件

        把and中的and改爲or即可:

/**
 * 轉換爲Predicate
 */
@SuppressWarnings("unchecked")
private Predicate toOrPredicate(Root<T> root,CriteriaBuilder criteriaBuilder) {
    Predicate restrictions = criteriaBuilder.disjunction();
    if (root == null || CollectionUtils.isEmpty(andFilters)) {
        return restrictions;
    }
    for (Filter filter : orFilters) {
        if (filter == null) {
            continue;
        }
        String property = filter.getProperty();
        Filter.Operator operator = filter.getOperator();
        Object value = filter.getValue();
        Boolean ignoreCase = filter.getIgnoreCase();
        Path<?> path = getPath(root, property);
        if (path == null) {
            continue;
        }
        switch (operator) {
            case eq:
                if (value != null) {
                    if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.equal(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.equal(path, value));
                    }
                } else {
                    restrictions = criteriaBuilder.or(restrictions, path.isNull());
                }
                break;
            case ne:
                if (value != null) {
                    if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.notEqual(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.notEqual(path, value));
                    }
                } else {
                    restrictions = criteriaBuilder.or(restrictions, path.isNotNull());
                }
                break;
            case gt:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.gt((Path<Number>) path, (Number) value));
                }
                break;
            case lt:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) value));
                }
                break;
            case ge:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) value));
                }
                break;
            case le:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.le((Path<Number>) path, (Number) value));
                }
                break;
            case like:
                if (String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                    if (BooleanUtils.isTrue(ignoreCase)) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like((Path<String>) path, (String) value));
                    }
                }
                break;
            case in:
                restrictions = criteriaBuilder.or(restrictions, path.in(value));
                break;
            case isNull:
                restrictions = criteriaBuilder.or(restrictions, path.isNull());
                break;
            case isNotNull:
                restrictions = criteriaBuilder.or(restrictions, path.isNotNull());
                break;
        }
    }
    return restrictions;
}

4.分析排序條件

/**
 * 轉換爲Order
 */
private List<javax.persistence.criteria.Order> toOrders(Root<T> root,CriteriaBuilder criteriaBuilder) {
    List<javax.persistence.criteria.Order> orderList = new ArrayList<javax.persistence.criteria.Order>();
    if (root == null || CollectionUtils.isEmpty(orders)) {
        return orderList;
    }
    for (Order order : orders) {
        if (order == null) {
            continue;
        }
        String property = order.getProperty();
        Order.Direction direction = order.getDirection();
        Path<?> path = getPath(root, property);
        if (path == null || direction == null) {
            continue;
        }
        switch (direction) {
            case asc:
                orderList.add(criteriaBuilder.asc(path));
                break;
            case desc:
                orderList.add(criteriaBuilder.desc(path));
                break;
        }
    }
    return orderList;
}

最後在toPredicate方法中構造最終條件:

/**
 * 生成條件的
 * @param root 該對象的封裝
 * @param query 查詢構建器
 * @param cb 構建器
 * @return 條件集合
 */
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
    Predicate restrictions = cb.and(toAndPredicate(root,cb));
    restrictions = cb.and(restrictions,toOrPredicate(root,cb));
    query.orderBy(toOrders(root,cb));
    return restrictions;
}

加上方便的鏈式調用方法:

/**
 * 添加一個and條件
 * @param filter 該條件
 * @return 鏈式調用
 */
public  QueryParams and(Filter filter){
    this.andFilters.add(filter);
    return this;
}
/**
 * 添加多個and條件
 * @param filter 該條件
 * @return 鏈式調用
 */
public  QueryParams and(Filter ...filter){
    this.andFilters.addAll(Arrays.asList(filter));
    return this;
}
/**
 * 添加一個or條件
 * @param filter 該條件
 * @return 鏈式調用
 */
public  QueryParams or(Filter filter){
    this.orFilters.add(filter);
    return this;
}
/**
 * 添加多個or條件
 * @param filter 該條件
 * @return 鏈式調用
 */
public  QueryParams or(Filter ...filter){
    this.orFilters.addAll(Arrays.asList(filter));
    return this;
}
/**
 * 升序字段
 * @param property 該字段對應變量名
 * @return 鏈式調用
 */
public  QueryParams orderASC(String property){
    this.orders.add(Order.asc(property));
    return this;
}
/**
 * 降序字段
 * @param property 該字段對應變量名
 * @return 鏈式調用
 */
public  QueryParams orderDESC(String property){
    this.orders.add(Order.desc(property));
    return this;
}
​
/**
 * 清除所有條件
 * @return 該實例
 */
public QueryParams clearAll(){
    if (!this.andFilters.isEmpty()) this.andFilters.clear();
    if (!this.orFilters.isEmpty()) this.orFilters.clear();
    if (!this.orders.isEmpty()) this.orders.clear();
    return this;
}
/**
 * 清除and條件
 * @return 該實例
 */
public QueryParams clearAnd(){
    if (!this.andFilters.isEmpty()) this.andFilters.clear();
    return this;
}
/**
 * 清除or條件
 * @return 該實例
 */
public QueryParams clearOr(){
    if (!this.orFilters.isEmpty()) this.andFilters.clear();
    return this;
}
/**
 * 清除order條件
 * @return 該實例
 */
public QueryParams clearOrder(){
    if (!this.orders.isEmpty()) this.orders.clear();
    return this;
}
//省略get和set方法

4.測試

        首先讓PcardOrderRepository接口繼承加上JpaSpecificationExecutor:

public interface PcardOrderRepository extends JpaRepository<PcardOrder,String>,PcardOrderRepositoryCustom,JpaSpecificationExecutor<PcardOrder> {
}

編寫測試代碼,這個使用的是CriteriaBuilder構建查詢的,所以查詢字段都是JPQL字段,並不是原生sql:

QueryParams<PcardOrder> queryParams = new QueryParams<>();
//使用Specification條件查詢,使用JPQL字段查詢
queryParams
        .and(Filter.eq("acctId","0014779934917371041"),Filter.ne("orderAmt",0L),
                Filter.eq("orderRespCd","00"))
        .or(Filter.eq("orderTypeId","A003"),Filter.eq("orderTypeId","A007"),
                Filter.eq("orderTypeId","A021"),Filter.eq("orderTypeId","A018"))
        .orderDESC("createTime");
​
Page<PcardOrder> JPQLlist = pcardOrderRepository.findAll(queryParams,new PageRequest(0,2));
​
//構造出來的條件
where
1=1
and pcardorder0_.acct_id=?
and pcardorder0_.order_amt<>0
and pcardorder0_.order_resp_cd=?
and (
    0=1
    or pcardorder0_.order_type_id=?
    or pcardorder0_.order_type_id=?
    or pcardorder0_.order_type_id=?
    or pcardorder0_.order_type_id=?
)
order by
pcardorder0_.create_time desc limit ?

3.原生sql查詢

        還是利用上面的Filter,具體還是遍歷+拼接,示例中我卸載了公共方法中,需要使用的Impl直接extends即可。

1.解析條件

        解析條件實際上就是拼接sql,代碼很簡單:

/**
 * 公共方法的repository
 */
@NoRepositoryBean
public class BaseRepository {
​
    private static Logger logger = LoggerFactory.getLogger(BaseRepository.class);
​
    /**
     * 分析查詢參數,並且合併到sql語句中
     * @param sql JPQL查詢語句
     * @param params 查詢參數
     * @return 參數對應的value
     */
    @SuppressWarnings("Unchecked")
    protected List<Object> analysisQueryParams(StringBuilder sql, QueryParams<?> params){
        List<String> strList = new ArrayList<>();
        List<Object> valueList = new ArrayList<>();
        int i = 1;
        //分析or條件
        for (Filter filter : params.getOrFilters()) {
            if (filter.getValue() != null){
                strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" ?" + (i++));
                valueList.add(filter.getValue());
            }else {
                strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" ");
            }
        }
        if (!strList.isEmpty()){
            sql.append(" and ").append("( ").append(StringUtils.join(strList," or ")).append(" )");
        }
        strList.clear();
        //分析and條件
        for (Filter filter : params.getAndFilters()) {
            if (filter.getValue() != null){
                strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" ?" + (i++));
                valueList.add(filter.getValue());
            }else {
                strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" ");
            }
        }
        sql.append(" and ").append(StringUtils.join(strList," and "));
        //分析排序字段
        if (!params.getOrders().isEmpty()){
            sql.append(" order by ");
            sql.append(StringUtils.join(params.getOrders(),","));
        }
        logger.debug("解析後的sql:"+sql.toString());
        logger.debug("對應的值爲:"+valueList);
        return valueList;
    }
​
}

2.測試

        在自定義接口中加入方法:

public interface PcardOrderRepositoryCustom {
    List findByQueryParam(QueryParams queryParams, Pageable pageable);
}

然後實現類中需要寫部分sql

@NoRepositoryBean
public class PcardOrderRepositoryImpl extends BaseRepository implements PcardOrderRepositoryCustom {
​
    @PersistenceContext
    private EntityManager entityManager;
    @Override
    public List findByQueryParam(QueryParams queryParams, Pageable pageable) {
        StringBuilder sql = new StringBuilder("select * from tbl_pcard_order where 1=1 ");
        List values = analysisQueryParams(sql,queryParams);
        Query query = entityManager.createNativeQuery(sql.toString());
        for (int i = 0; i < values.size(); i++) {
            query.setParameter(i+1,values.get(i));
        }
        query.setFirstResult(pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());
        return query.getResultList();
    }
}

測試代碼:

//使用原生sql查詢,注意這裏使用原生sql字段,並非JPQL字段,
//本質是根據BaseRepository.analysisQueryParams 來拼接條件,可以根據自己的需求更改
queryParams.clearAll()
        .and(Filter.eq("acct_id","0014779934917371041"),Filter.ne("order_amt",0L),
                Filter.eq("order_resp_cd","00"))
        .or(Filter.eq("order_type_id","A003"),Filter.eq("order_type_id","A007"),
                Filter.eq("order_type_id","A021"),Filter.eq("order_type_id","A018"))
        .orderDESC("create_time");
List nativeSqlList = pcardOrderRepository.findByQueryParam(queryParams,new PageRequest(0,2));
//構造出來的sql
where
1=1 
and (
    order_type_id  =  ?
    or order_type_id  =  ?
    or order_type_id  =  ?
    or order_type_id  =  ?
)
and acct_id  =  ?
and order_amt  !=  ?
and order_resp_cd  =  ?
order by
create_time desc limit ?

4.使用原生sql查詢出Map集合

        使用原生sql進行表關聯查詢,返回值一般都用List:

public List<Object[]> findById(int id) {
    String sql = "select u.*,c.* from user u,commont c where u.id = c.id and id=?1";
    Query query = entityManager.createNativeQuery(sql);
    query.setParameter(1,id);
    return query.getResultList();
}

那麼就要改進,使其返回Map:

public List findById (int id) {
    String sql = "select u.*,c.* from user u,commont c where u.id = c.id and id=?1";
    Query query = entityManager.createNativeQuery(sql);
    query.setParameter(1,id);
    //轉換爲Map集合
    query.unwrap(org.hibernate.SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
    return query.getResultList();
}
​
//實際上返回的是一個List<Map>集合

這樣的返回值是一個List類型,取出的時候直接根據鍵值取即可。

四、項目實戰

1、基本配置

        參考《 Spring Boot整理——Thymeleaf模板》的項目,首先修改build.gradle的配置如下:

// buildscript 代碼塊中腳本優先執行
buildscript {
​
    // ext 用於定義動態屬性
    ext {
        springBootVersion = '1.5.2.RELEASE'
    }

    // 自定義  Thymeleaf 和 Thymeleaf Layout Dialect 的版本
    ext['thymeleaf.version'] = '3.0.3.RELEASE'
    ext['thymeleaf-layout-dialect.version'] = '2.2.0'

    // 自定義  Hibernate 的版本
    ext['hibernate.version'] = '5.2.8.Final'

    // 使用了 Maven 的中央倉庫(你也可以指定其他倉庫)
    repositories {
        //mavenCentral()
        maven {
            url 'http://maven.aliyun.com/nexus/content/groups/public/'
        }
    }

    // 依賴關係
    dependencies {
        // classpath 聲明說明了在執行其餘的腳本時,ClassLoader 可以使用這些依賴項
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
​
// 使用插件
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
​
// 打包的類型爲 jar,並指定了生成的打包的文件名稱和版本
jar {
    baseName = 'jpa-in-action'
    version = '1.0.0'
}
​
// 指定編譯 .java 文件的 JDK 版本
sourceCompatibility = 1.8
​
// 默認使用了 Maven 的中央倉庫。這裏改用自定義的鏡像庫
repositories {
    //mavenCentral()
    maven {
        url 'http://maven.aliyun.com/nexus/content/groups/public/'
    }
}
​
// 依賴關係
dependencies {
    // 該依賴對於編譯發行是必須的
    compile('org.springframework.boot:spring-boot-starter-web')

    // 添加 Thymeleaf 的依賴
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
​
    // 添加 Spring Data JPA 的依賴
    compile('org.springframework.boot:spring-boot-starter-data-jpa')

    // 添加 MySQL連接驅動 的依賴
    compile('mysql:mysql-connector-java:6.0.5')
​
    // 該依賴對於編譯測試是必須的,默認包含編譯產品依賴和編譯時依
    testCompile('org.springframework.boot:spring-boot-starter-test')

    //添加H2的依賴
    runtime('com.h2database:h2:1.4.193')
}

然後application.properties爲:

# THYMELEAF 
spring.thymeleaf.encoding=UTF-8
# 熱部署靜態文件
spring.thymeleaf.cache=false
# 使用HTML5標準
spring.thymeleaf.mode=HTML5
​
#使用H2 控制檯   訪問頁面http://localhost:8080/h2-console/,
spring.h2.console.enabled=true
​
# DataSource 測試時可以先不用mysql數據庫
#spring.datasource.url=jdbc:mysql://localhost/blog?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC 
#spring.datasource.username=root
#spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
​
# JPA
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto=create-drop

注意:在測試階段可以使用H2數據庫,其管理頁面如下(http://localhost:8080/h2-console/  ),默認的數據庫爲jdbc:h2:mem:testdb,如果啓用mysql,把mysql註釋放開即可。

2、實體改造

@Entity  // 實體
public class User implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id  // 主鍵
    @GeneratedValue(strategy=GenerationType.IDENTITY) // 自增長策略
    private Long id; // 用戶的唯一標識
​
    @Column(nullable = false) // 映射爲字段,值不能爲空
    private String name;

    @Column(nullable = false)
    private Integer age;
​
    @Override
    public String toString() {
        return String.format(
                "User[id=%d, name='%s', age='%d']",
                id, name, age);
    }
}

3、持久化層改造

接口改成如下內容,實現類刪除,用spring 提供的

/**
 * 用戶倉庫.
 */
public interface UserRepository extends CrudRepository<User, Long>{
}

4、控制層改造

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired 
    private UserRepository userRepository;
​
    /**
     * 從 用戶存儲庫 獲取用戶列表
     * @return
     */
    private List<User> getUserlist() {
        List<User> users = new ArrayList<>();
        for (User user : userRepository.findAll()) {
            users.add(user);
        }
        return users;
    }
​
    /**
     * 查詢所用用戶
     * @return
     */
    @GetMapping
    public ModelAndView list(Model model) {
        model.addAttribute("userList", getUserlist());
        model.addAttribute("title", "用戶管理");
        return new ModelAndView("users/list", "userModel", model);
    }

    /**
     * 根據id查詢用戶
     * @param message
     * @return
     */
    @GetMapping("{id}")
    public ModelAndView view(@PathVariable("id") Long id, Model model) {
        User user = userRepository.findOne(id);
        model.addAttribute("user", user);
        model.addAttribute("title", "查看用戶");
        return new ModelAndView("users/view", "userModel", model);
    }
​
    /**
     * 獲取 form 表單頁面
     * @param user
     * @return
     */
    @GetMapping("/form")
    public ModelAndView createForm(Model model) {
        model.addAttribute("user", new User(null, null));
        model.addAttribute("title", "創建用戶");
        return new ModelAndView("users/form", "userModel", model);
    }
​
    /**
     * 新建用戶
     * @param user
     * @param result
     * @param redirect
     * @return
     */
    @PostMapping
    public ModelAndView create(User user) {
        userRepository.save(user);
        return new ModelAndView("redirect:/users");
    }
​
    /**
     * 刪除用戶
     * @param id
     * @return
     */
    @GetMapping(value = "delete/{id}")
    public ModelAndView delete(@PathVariable("id") Long id, Model model) {
        userRepository.delete(id);
        model.addAttribute("userList", getUserlist());
        model.addAttribute("title", "刪除用戶");
        return new ModelAndView("users/list", "userModel", model);
    }
​
    /**
     * 修改用戶
     * @param user
     * @return
     */
    @GetMapping(value = "modify/{id}")
    public ModelAndView modifyForm(@PathVariable("id") Long id, Model model) {
        User user = userRepository.findOne(id);
        model.addAttribute("user", user);
        model.addAttribute("title", "修改用戶");
        return new ModelAndView("users/form", "userModel", model);
    }
​
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章