Java動態拼接SQL--02--Jpa


本篇進行Spring-data-jpa的介紹,幾乎涵蓋該框架的所有方面,在日常的開發當中,基本上能滿足所有需求。這裏不講解JPA和Spring-data-jpa單獨使用,所有的內容都是在和Spring整合的環境中實現。如果需要了解該框架的入門,百度一下,很多入門的介紹。在這篇文章的接下來一篇,會有一個系列來講解mybatis,這個系列從mybatis的入門開始,到基本使用,和spring整合,和第三方插件整合,緩存,插件,最後會持續到mybatis的架構,源碼解釋,重點會介紹幾個重要的設計模式,這樣一個體系。基本上講完之後,mybatis在你面前就沒有了祕密,你能解決mybatis的幾乎所有問題,並且在開發過程中相當的方便,駕輕就熟。

這篇文章由於介紹的類容很全,因此很長,如果你需要,那麼可以耐心的看完,本人經歷了很長時間的學識,使用,研究的心血濃縮成爲這麼短短的一篇博客。

大致整理一個提綱:

  1、Spring-data-jpa的基本介紹;

  2、和Spring整合;

  3、基本的使用方式;

  4、複雜查詢,包括多表關聯,分頁,排序等;

現在開始:

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

  上面闡述了JPA和Hibernate的關係,那麼Spring-data-jpa又是個什麼東西呢?這地方需要稍微解釋一下,我們做Java開發的都知道Spring的強大,到目前爲止,企業級應用Spring幾乎是無所不能,無所不在,已經是事實上的標準了,企業級應用不使用Spring的幾乎沒有,這樣說沒錯吧。而Spring整合第三方框架的能力又很強,他要做的不僅僅是個最早的IOC容器這麼簡單一回事,現在Spring涉及的方面太廣,主要是體現在和第三方工具的整合上。而在與第三方整合這方面,Spring做了持久化這一塊的工作,我個人的感覺是Spring希望把持久化這塊內容也拿下。於是就有了Spring-data-**這一系列包。包括,Spring-data-jpa,Spring-data-template,Spring-data-mongodb,Spring-data-redis,還有個民間產品,mybatis-spring,和前面類似,這是和mybatis整合的第三方包,這些都是乾的持久化工具乾的事兒。

  這裏介紹Spring-data-jpa,表示與jpa的整合。

  2、我們都知道,在使用持久化工具的時候,一般都有一個對象來操作數據庫,在原生的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語句,至少在我看來,企業級應用基本上可以不用寫任何一條sql,當然spring-data-jpa也提供自己寫sql的方式,這個就看個人怎麼選擇,都可以。我覺得都行。

  2.1與Spring整合我們從spring配置文件開始,爲了節省篇幅,這裏我只寫出配置文件的結構。

複製代碼
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:mongo="http://www.springframework.org/schema/data/mongo"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/aop     
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd   
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
           http://www.springframework.org/schema/context     
           http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/data/mongo
           http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
           http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <!-- 數據庫連接 -->
    <context:property-placeholder location="classpath:your-config.properties" ignore-unresolvable="true" />
    <!-- service包 -->
    <context:component-scan base-package="your service package" />
    <!-- 使用cglib進行動態代理 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
    <!-- 支持註解方式聲明式事務 -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
    <!-- dao -->
    <jpa:repositories base-package="your dao package" repository-impl-postfix="Impl" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager" />
    <!-- 實體管理器 -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="your entity package" />
        <property name="persistenceProvider">
            <bean class="org.hibernate.ejb.HibernatePersistence" />
        </property>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="false" />
                <property name="database" value="MYSQL" />
                <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
                <!-- <property name="showSql" value="true" /> -->
            </bean>
        </property>
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
        </property>
        <property name="jpaPropertyMap">
            <map>
                <entry key="hibernate.query.substitutions" value="true 1, false 0" />
                <entry key="hibernate.default_batch_fetch_size" value="16" />
                <entry key="hibernate.max_fetch_depth" value="2" />
                <entry key="hibernate.generate_statistics" value="true" />
                <entry key="hibernate.bytecode.use_reflection_optimizer" value="true" />
                <entry key="hibernate.cache.use_second_level_cache" value="false" />
                <entry key="hibernate.cache.use_query_cache" value="false" />
            </map>
        </property>
    </bean>
    
    <!-- 事務管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    
    <!-- 數據源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${driver}" />
        <property name="url" value="${url}" />
        <property name="username" value="${userName}" />
        <property name="password" value="${password}" />
        <property name="initialSize" value="${druid.initialSize}" />
        <property name="maxActive" value="${druid.maxActive}" />
        <property name="maxIdle" value="${druid.maxIdle}" />
        <property name="minIdle" value="${druid.minIdle}" />
        <property name="maxWait" value="${druid.maxWait}" />
        <property name="removeAbandoned" value="${druid.removeAbandoned}" />
        <property name="removeAbandonedTimeout" value="${druid.removeAbandonedTimeout}" />
        <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />
        <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
        <property name="validationQuery" value="${druid.validationQuery}" />
        <property name="testWhileIdle" value="${druid.testWhileIdle}" />
        <property name="testOnBorrow" value="${druid.testOnBorrow}" />
        <property name="testOnReturn" value="${druid.testOnReturn}" />
        <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" />
        <property name="filters" value="${druid.filters}" />
    </bean>
    
    <!-- 事務 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" />
            <tx:method name="get*" read-only="true" />
            <tx:method name="find*" read-only="true" />
            <tx:method name="select*" read-only="true" />
            <tx:method name="delete*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="insert*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>
    <!-- 事務入口 -->
    <aop:config>
        <aop:pointcut id="allServiceMethod" expression="execution(* your service implements package.*.*(..))" />
        <aop:advisor pointcut-ref="allServiceMethod" advice-ref="txAdvice" />
    </aop:config>

</beans>
複製代碼

  2.2對上面的配置文件進行簡單的解釋,只對「實體管理器」和「dao」進行解釋,其他的配置在任何地方都差不太多。

    1.對「實體管理器」解釋:我們知道原生的jpa的配置信息是必須放在META-INF目錄下面的,並且名字必須叫做persistence.xml,這個叫做persistence-unit,就叫做持久化單元,放在這下面我們感覺不方便,不好,於是Spring提供了

org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean

這樣一個類,可以讓你的隨心所欲的起這個配置文件的名字,也可以隨心所欲的修改這個文件的位置,只需要在這裏指向這個位置就行。然而更加方便的做法是,直接把配置信息就寫在這裏更好,於是就有了這實體管理器這個bean。使用

<property name="packagesToScan" value="your entity package" />

這個屬性來加載我們的entity。

  2.3 解釋「dao」這個bean。這裏衍生一下,進行一下名詞解釋,我們知道dao這個層叫做Data Access Object,數據庫訪問對象,這是一個廣泛的詞語,在jpa當中,我們還有一個詞語叫做Repository,這裏我們一般就用Repository結尾來表示這個dao,比如UserDao,這裏我們使用UserRepository,當然名字無所謂,隨意取,你可以意會一下我的意思,感受一下這裏的含義和區別,同理,在mybatis中我們一般也不叫dao,mybatis由於使用xml映射文件(當然也提供註解,但是官方文檔上面表示在有些地方,比如多表的複雜查詢方面,註解還是無解,只能xml),我們一般使用mapper結尾,比如我們也不叫UserDao,而叫UserMapper。

  上面拓展了一下關於dao的解釋,那麼這裏的這個配置信息是什麼意思呢?首先base-package屬性,代表你的Repository接口的位置,repository-impl-postfix屬性代表接口的實現類的後綴結尾字符,比如我們的UserRepository,那麼他的實現類就叫做UserRepositoryImpl,和我們平時的使用習慣完全一致,於此同時,spring-data-jpa的習慣是接口和實現類都需要放在同一個包裏面(不知道有沒有其他方式能分開放,這不是重點,放在一起也無所謂,影響不大),再次的,這裏我們的UserRepositoryImpl這個類的定義的時候我們不需要去指定實現UserRepository接口,根據spring-data-jpa自動就能判斷二者的關係。

  比如:我們的UserRepository和UserRepositoryImpl這兩個類就像下面這樣來寫。

public interface UserRepository extends JpaRepository<User, Integer>{}
public class UserRepositoryImpl {}

  那麼這裏爲什麼要這麼做呢?原因是:spring-data-jpa提供基礎的CRUD工作,同時也提供業務邏輯的功能(前面說了,這是該框架的威力所在),所以我們的Repository接口要做兩項工作,繼承spring-data-jpa提供的基礎CRUD功能的接口,比如JpaRepository接口,同時自己還需要在UserRepository這個接口中定義自己的方法,那麼導致的結局就是UserRepository這個接口中有很多的方法,那麼如果我們的UserRepositoryImpl實現了UserRepository接口,導致的後果就是我們勢必需要重寫裏面的所有方法,這是Java語法的規定,如此一來,悲劇就產生了,UserRepositoryImpl裏面我們有很多的@Override方法,這顯然是不行的,結論就是,這裏我們不用去寫implements部分。

  spring-data-jpa實現了上面的能力,那他是怎麼實現的呢?這裏我們通過源代碼的方式來呈現他的來龍去脈,這個過程中cglib發揮了傑出的作用。

  在spring-data-jpa內部,有一個類,叫做

public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>,
        JpaSpecificationExecutor<T>

我們可以看到這個類是實現了JpaRepository接口的,事實上如果我們按照上面的配置,在同一個包下面有UserRepository,但是沒有UserRepositoryImpl這個類的話,在運行時期UserRepository這個接口的實現就是上面的SimpleJpaRepository這個接口。而如果有UserRepositoryImpl這個文件的話,那麼UserRepository的實現類就是UserRepositoryImpl,而UserRepositoryImpl這個類又是SimpleJpaRepository的子類,如此一來就很好的解決了上面的這個不用寫implements的問題。我們通過閱讀這個類的源代碼可以發現,裏面包裝了entityManager,底層的調用關係還是entityManager在進行CRUD。

  3. 下面我們通過一個完整的項目來基本使用spring-data-jpa,然後我們在介紹他的高級用法。

  a.數據庫建表:user,主鍵自增

  

  b.對應實體:User

複製代碼
@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private String password;
    private String birthday;
    // getter,setter
}
複製代碼

  c.簡歷UserRepository接口

public interface UserRepository extends JpaRepository<User, Integer>{}

  通過上面3步,所有的工作就做完了,User的基礎CRUD都能做了,簡約而不簡單。

  d.我們的測試類UserRepositoryTest

複製代碼
public class UserRepositoryTest {
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    public void baseTest() throws Exception {
        User user = new User();
        user.setName("Jay");
        user.setPassword("123456");
        user.setBirthday("2008-08-08");
        userRepository.save(user);
//        userRepository.delete(user);
//        userRepository.findOne(1);
    }
}
複製代碼

  測試通過。

  說到這裏,和spring已經完成。接下來第三點,基本使用。

4.前面把基礎的東西說清楚了,接下來就是spring-data-jpa的正餐了,真正威力的地方。

  4.1 我們的系統中一般都會有用戶登錄這個接口,在不使用spring-data-jpa的時候我們怎麼做,首先在service層定義一個登錄方法。如:

User login(String name, String password);

然後在serviceImpl中寫該方法的實現,大致這樣:

    @Override
    public User login(String name, String password) {
        return userDao.login(name, password);
    }

接下來,UserDao大概是這麼個樣子:

User getUserByNameAndPassword(String name, String password);

然後在UserDaoImpl中大概是這麼個樣子:

    public User getUserByNameAndPassword(String name, String password) {
        Query query = em.createQuery("select * from User t where t.name = ?1 and t.password = ?2");
        query.setParameter(1, name);
        query.setParameter(2, password);
        return (User) query.getSingleResult();
    }

ok,這個代碼運行良好,那麼這樣子大概有十來行代碼,我們感覺這個功能實現了,很不錯。然而這樣子真正簡捷麼?如果這樣子就滿足了,那麼spring-data-jpa就沒有必要存在了,前面提到spring-data-jpa能夠幫助你完成業務邏輯代碼的處理,那他是怎麼處理的呢?這裏我們根本不需要UserDaoImpl這個類,只需要在UserRepository接口中定義一個方法

User findByNameAndPassword(String name, String password);

然後在service中調用這個方法就完事了,所有的邏輯只需要這麼一行代碼,一個沒有實現的接口方法。通過debug信息,我們看到輸出的sql語句是

select * from user where name = ? and password = ?

跟上面的傳統方式一模一樣的結果。這簡單到令人髮指的程度,那麼這一能力是如何實現的呢?原理是:spring-data-jpa會根據方法的名字來自動生成sql語句,我們只需要按照方法定義的規則即可,上面的方法findByNameAndPassword,spring-data-jpa規定,方法都以findBy開頭,sql的where部分就是NameAndPassword,被spring-data-jpa翻譯之後就編程了下面這種形態:

where name = ? and password = ?

在舉個例,如果是其他的操作符呢,比如like,前端模糊查詢很多都是以like的方式來查詢。比如根據名字查詢用戶,sql就是

select * from user where name like = ?

這裏spring-data-jpa規定,在屬性後面接關鍵字,比如根據名字查詢用戶就成了

User findByNameLike(String name);

被翻譯之後的sql就是

select * from user where name like = ?

這也是簡單到令人髮指,spring-data-jpa所有的語法規定如下圖:

通過上面,基本CRUD和基本的業務邏輯操作都得到了解決,我們要做的工作少到僅僅需要在UserRepository接口中定義幾個方法,其他所有的工作都由spring-data-jpa來完成。

 接下來:就是比較複雜的操作了,比如動態查詢,分頁,下面詳細介紹spring-data-jpa的第二大殺手鐗,強大的動態查詢能力。

在上面的介紹中,對於我們傳統的企業級應用的基本操作已經能夠基本上全部實現,企業級應用一般都會有一個模糊查詢的功能,並且是多條的查詢,在有查詢條件的時候我們需要在where後面接上一個 xxx = yyy 或者 xxx like '% + yyy + %'類似這樣的sql。那麼我們傳統的JDBC的做法是使用很多的if語句根據傳過來的查詢條件來拼sql,mybatis的做法也類似,由於mybatis有強大的動態xml文件的標籤,在處理這種問題的時候顯得非常的好,但是二者的原理都一致,那spring-data-jpa的原理也同樣很類似,這個道理也就說明了解決多表關聯動態查詢根兒上也就是這麼回事。

  那麼spring-data-jpa的做法是怎麼的呢?有兩種方式。可以選擇其中一種,也可以結合使用,在一般的查詢中使用其中一種就夠了,就是第二種,但是有一類查詢比較棘手,比如報表相關的,報表查詢由於涉及的表很多,這些表不一定就是兩兩之間有關係,比如字典表,就很獨立,在這種情況之下,使用拼接sql的方式要容易一些。下面分別介紹這兩種方式。

  a.使用JPQL,和Hibernate的HQL很類似。

   前面說道了在UserRepository接口的同一個包下面建立一個普通類UserRepositoryImpl來表示該類的實現類,同時前面也介紹了完全不需要這個類的存在,但是如果使用JPQL的方式就必須要有這個類。如下:

複製代碼
public class StudentRepositoryImpl {
    
    @PersistenceContext
    private EntityManager em;
    @SuppressWarnings("unchecked")
    public Page<Student> search(User user) {
        String dataSql = "select t from User t where 1 = 1";
        String countSql = "select count(t) from User t where 1 = 1";
        
        if(null != user && !StringUtils.isEmpty(user.getName())) {
            dataSql += " and t.name = ?1";
            countSql += " and t.name = ?1";
        }
        
        Query dataQuery = em.createQuery(dataSql);
        Query countQuery = em.createQuery(countSql);
        
        if(null != user && !StringUtils.isEmpty(user.getName())) {
            dataQuery.setParameter(1, user.getName());
            countQuery.setParameter(1, user.getName());
        }long totalSize = (long) countQuery.getSingleResult();
        Page<User> page = new Page();
        page.setTotalSize(totalSize);
        List<User> data = dataQuery.getResultList();
        page.setData(data);
        return page;
    }
    
}
複製代碼

通過上面的方法,我們查詢並且封裝了一個User對象的分頁信息。代碼能夠良好的運行。這種做法也是我們傳統的經典做法。那麼spring-data-jpa還有另外一種更好的方式,那就是所謂的類型檢查的方式,上面我們的sql是字符串,沒有進行類型檢查,而下面的方式就使用了類型檢查的方式。這個道理在mybatis中也有體現,mybatis可以使用字符串sql的方式,也可以使用接口的方式,而mybatis的官方推薦使用接口方式,因爲有類型檢查,會更安全。

  b.使用JPA的動態接口,下面的接口我把註釋刪了,爲了節省篇幅,註釋也沒什麼用,看方法名字大概都能猜到是什麼意思。

複製代碼
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);
}
複製代碼

 上面說了,使用這種方式我們壓根兒就不需要UserRepositoryImpl這個類,說到這裏,彷彿我們就發現了spring-data-jpa爲什麼把Repository和RepositoryImpl文件放在同一個包下面,因爲我們的應用很可能根本就一個Impl文件都不存在,那麼在那個包下面就只有一堆接口,即使把Repository和RepositoryImpl都放在同一個包下面,也不會造成這個包下面有正常情況下2倍那麼多的文件,根本原因:只有接口而沒有實現類。

上面我們的UserRepository類繼承了JpaRepository和JpaSpecificationExecutor類,而我們的UserRepository這個對象都會注入到UserService裏面,於是如果使用這種方式,我們的邏輯直接就寫在service裏面了,下面的代碼:一個學生Student類,一個班級Clazz類,Student裏面有一個對象Clazz,在數據庫中是clazz_id,這是典型的多對一的關係。我們在配置好entity裏面的關係之後。就可以在StudentServiceImpl類中做Student的模糊查詢,典型的前端grid的模糊查詢。代碼是這樣子的:

複製代碼
@Service
public class StudentServiceImpl extends BaseServiceImpl<Student> implements StudentService {
    
    @Autowired
    private StudentRepository studentRepository;
    
    @Override
    public Student login(Student student) {
        return studentRepository.findByNameAndPassword(student.getName(), student.getPassword());
    }

    @Override
    public Page<Student> search(final Student student, PageInfo page) {
        return studentRepository.findAll(new Specification<Student>() {
            @Override
            public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                
                Predicate stuNameLike = null;
                if(null != student && !StringUtils.isEmpty(student.getName())) {
           // 這裏也可以root.get("name").as(String.class)這種方式來強轉泛型類型 stuNameLike
= cb.like(root.<String> get("name"), "%" + student.getName() + "%"); } Predicate clazzNameLike = null; if(null != student && null != student.getClazz() && !StringUtils.isEmpty(student.getClazz().getName())) { clazzNameLike = cb.like(root.<String> get("clazz").<String> get("name"), "%" + student.getClazz().getName() + "%"); } if(null != stuNameLike) query.where(stuNameLike); if(null != clazzNameLike) query.where(clazzNameLike); return null; } }, new PageRequest(page.getPage() - 1, page.getLimit(), new Sort(Direction.DESC, page.getSortName()))); } }
複製代碼

先解釋下這裏的意思,然後我們在結合框架的源碼來深入分析。

這裏我們是2個表關聯查詢,查詢條件包括Student表和Clazz表,類似的2個以上的表方式差不多,但是正如上面所說,這種做法適合所有的表都是兩兩能夠關聯上的,涉及的表太多,或者是有一些字典表,那就使用sql拼接的方式,簡單一些。

先簡單解釋一下代碼的含義,然後結合框架源碼來詳細分析。兩個Predicate對象,Predicate按照中文意思是判斷,斷言的意思,那麼放在我們的sql中就是where後面的東西,比如

name like '% + jay + %';

下面的PageRequest代表分頁信息,PageRequest裏面的Sort對象是排序信息。上面的代碼事實上是在動態的組合最終的sql語句,這裏使用了一個策略模式,或者callback,就是

studentRepository.findAll(一個接口)

studentRepository接口方法調用的參數是一個接口,而接口的實現類調用這個方法的時候,在內部,參數對象的實現類調用自己的toPredicate這個方法的實現內容,可以體會一下這裏的思路,就是傳一個接口,然後接口的實現自己來定義,這個思路在nettyJavaScript中體現的特別明顯,特別是JavaScript的框架中大量的這種方式,JS框架很多的做法都是上來先閉包,和瀏覽器的命名空間分開,然後入口方法就是一個回調,比如ExtJS:

Ext.onReady(function() {
    // xxx
});

參數是一個function,其實在框架內部就調用了這個參數,於是這個這個方法執行了。這種模式還有一個JDK的排序集合上面也有體現,我們的netty框架也採用這種方式來實現異步IO的能力。

接下來結合框架源碼來詳細介紹這種機制,以及這種機制提供給我們的好處。

 這裏首先從JPA的動態查詢開始說起,在JPA提供的API中,動態查詢大概有這麼一些方法,

從名字大概可以看出這些方法的意義,跟Hibernate或者一些其他的工具也都差不多,這裏我們介紹參數爲CriteriaQuery類型的這個方法,如果我們熟悉多種ORM框架的話,不難發現都有一個Criteria類似的東西,中文意思是「條件」的意思,這就是各個框架構建動態查詢的主體,Hibernate甚至有兩種,在線和離線兩種Criteria,mybatis也能從Example中創建Criteria,並且添加查詢條件。

那麼第一步就需要構建出這個參數CriteriaQuery類型的參數,這裏使用建造者模式,

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Student> query = builder.createQuery(Student.class);

接下來:

Root<Student> root = query.from(Student.class);

在這裏,我們看方法名from,意思是獲取Student的Root,其實也就是個Student的包裝對象,就代表這條sql語句裏面的主體。接下來:

        Predicate p1 = builder.like(root.<String> get("name"), "%" + student.getName() + "%");
        Predicate p2 = builder.equal(root.<String> get("password"), student.getPassword());

Predicate是判斷的意思,放在sql語句中就是where後面 xxx = yyy, xxx like yyy這種,也就是查詢條件,這裏構造了2個查詢條件,分別是根據student的name屬性進行like查詢和根據student的password進行「=」查詢,在sql中就是

name like = ? and password = ?

這種形式,接下來

query.where(p1, p2);

這樣子一個完整的動態查詢就構建完成了,接下來調用getSingleResult或者getResultList返回結果,這裏jpa的單個查詢如果爲空的話會報異常,這點感覺框架設計的不好,如果查詢爲空直接返回一個null或者一個空的List更好一點。

這是jpa原生的動態查詢方式,過程大致就是,創建builder => 創建Query => 構造條件 => 查詢。這麼4個步驟,這裏代碼運行良好,如果不使用spring-data-jpa,我們就需要這麼來做,但是spring-data-jpa幫我們做得更爲徹底,從上面的4個步驟中,我們發現:所有的查詢除了第三步不一樣,其他幾步都是一模一樣的,不使用spring-data-jpa的情況下,我們要麼4步驟寫完,要麼自己寫個工具類,封裝一下,這裏spring-data-jpa就是幫我們完成的這樣一個動作,那就是在JpaSpecification<T>這個接口中的

Page<T> findAll(Specification<T> spec, Pageable pageable);

這個方法,前面說了,這是個策略模式,參數spec是個接口,前面也說了框架內部對於這個接口有默認的實現類

@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>,
        JpaSpecificationExecutor<T> {
   
}

,我們的Repository接口就是繼承這個接口,而通過cglib的RepositoryImpl的代理類也是這個類的子類,默認也就實現了該方法。這個方法的方法體是這樣的:

複製代碼
    /*
     * (non-Javadoc)
     * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findOne(org.springframework.data.jpa.domain.Specification)
     */
    public T findOne(Specification<T> spec) {

        try {
            return getQuery(spec, (Sort) null).getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }
複製代碼

這裏的

getQuery(spec, (Sort) null)

返回類型是

TypedQuery<T>

進入這個getQuery方法:

複製代碼
    /**
     * Creates a {@link TypedQuery} for the given {@link Specification} and {@link Sort}.
     * 
     * @param spec can be {@literal null}.
     * @param sort can be {@literal null}.
     * @return
     */
    protected TypedQuery<T> getQuery(Specification<T> spec, Sort sort) {

        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(getDomainClass());

        Root<T> root = applySpecificationToCriteria(spec, query);
        query.select(root);

        if (sort != null) {
            query.orderBy(toOrders(sort, root, builder));
        }

        return applyRepositoryMethodMetadata(em.createQuery(query));
    }
複製代碼

一切玄機盡收眼底,這個方法的內容和我們前面使用原生jpa的api的過程是一樣的,而再進入

Root<T> root = applySpecificationToCriteria(spec, query);

這個方法:

複製代碼
    /**
     * Applies the given {@link Specification} to the given {@link CriteriaQuery}.
     * 
     * @param spec can be {@literal null}.
     * @param query must not be {@literal null}.
     * @return
     */
    private <S> Root<T> applySpecificationToCriteria(Specification<T> spec, CriteriaQuery<S> query) {

        Assert.notNull(query);
        Root<T> root = query.from(getDomainClass());

        if (spec == null) {
            return root;
        }

        CriteriaBuilder builder = em.getCriteriaBuilder();
        Predicate predicate = spec.toPredicate(root, query, builder);

        if (predicate != null) {
            query.where(predicate);
        }

        return root;
    }
複製代碼

我們可以發現spec參數調用了toPredicate方法,也就是我們前面service裏面匿名內部類的實現。

到這裏spring-data-jpa的默認實現已經完全明瞭。總結一下使用動態查詢:前面說的原生api需要4步,而使用spring-data-jpa只需要一步,那就是重寫匿名內部類的toPredicate方法。在重複一下上面的Student和Clazz的查詢代碼,

複製代碼
 1     @Override
 2     public Page<Student> search(final Student student, PageInfo page) {
 4         return studentRepository.findAll(new Specification<Student>() {
 5             @Override
 6             public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
 7                 
 8                 Predicate stuNameLike = null;
 9                 if(null != student && !StringUtils.isEmpty(student.getName())) {
10                     stuNameLike = cb.like(root.<String> get("name"), "%" + student.getName() + "%");
11                 }
12                 
13                 Predicate clazzNameLike = null;
14                 if(null != student && null != student.getClazz() && !StringUtils.isEmpty(student.getClazz().getName())) {
15                     clazzNameLike = cb.like(root.<String> get("clazz").<String> get("name"), "%" + student.getClazz().getName() + "%");
16                 }
17                 
18                 if(null != stuNameLike) query.where(stuNameLike);
19                 if(null != clazzNameLike) query.where(clazzNameLike);
20                 return null;
21             }
22         }, new PageRequest(page.getPage() - 1, page.getLimit(), new Sort(Direction.DESC, page.getSortName())));
23     }
複製代碼

到這裏位置,spring-data-jpa的介紹基本上就完成了,涵蓋了該框架使用的方方面面。接下來還有一塊比較實用的東西,我們看到上面第15行位置的條件查詢,這裏使用了一個多級的get,這個是spring-data-jpa支持的,就是嵌套對象的屬性,這種做法一般我們叫方法的級聯調用,就是調用的時候返回自己本身,這個在處理xml的工具中比較常見,主要是爲了代碼的美觀作用,沒什麼其他的用途。

最後還有一個小問題,我們上面說了使用動態查詢和JPQL兩種方式都可以,在我們使用JPQL的時候,他的語法和常規的sql有點不太一樣,以Student、Clazz關係爲例,比如:

select * from student t left join clazz tt on t.clazz_id = tt.id

這是一個很常規的sql,但是JPQL是這麼寫:

select t from Student t left join t.clazz tt

left join右邊直接就是t的屬性,並且也沒有了on t.clazz_id == tt.id,然而並不會出現笛卡爾積,這裏解釋一下爲什麼沒有這個條件,在我們的實體中配置了屬性的映射關係,並且ORM框架的最核心的目的就是要讓我們以面向對象的方式來操作數據庫,顯然我們在使用這些框架的時候就不需要關心數據庫了,只需要關係對象,而t.clazz_id = tt.id這個是數據庫的字段,由於配置了字段映射,框架內部自己就會去處理,所以不需要on t.clazz_id = tt.id就是合理的。

  前面介紹了spring-data-jpa的使用,還有一點忘了,悲觀所和樂觀鎖問題,這裏的樂觀鎖比較簡單,jpa有提供註解@Version,加上該註解,自動實現樂觀鎖,byId修改的時候sql自動變成:update ... set ... where id = ? and version = ?,比較方便。

 in操作的查詢:

  在日常手動寫sql的時候有in這種查詢是比較多的,比如select * from user t where t.id in (1, 2, 3);有人說in的效率不高,要少用,但是其實只要in是主鍵,或者說是帶有索引的,效率是很高的,mysql中如果in是子查詢貌似不會走索引,不過我個人經驗,在我遇到的實際應用中,in(ids)這種是比較多的,所以一般來說是沒有性能問題的。

  那麼,sql裏面比較好寫,但是如果使用spring-data-jpa的動態查詢方式呢,就和前面的稍微有點區別。大致上是這麼一個思路:

複製代碼
if(!CollectionUtils.isEmpty(ids)) {
    In<Long> in = cb.in(root.<Long> get("id"));
    for (Long id : parentIds) {
        in.value(id);
    }
    query.where(in);
}
複製代碼

  cb創建一個in的Predicate,然後給這個in賦值,最後把in加到where條件中。

手動配置鎖:

  spring-data-jpa支持註解方式的sql,比如:@Query(xxx),另外,關於鎖的問題,在實體中的某個字段配置@Version是樂觀鎖,有時候爲了使用一個悲觀鎖,或者手動配置一個樂觀鎖(如果實體中沒有version字段),那麼可以使用@Lock這個註解,它能夠被解析成爲相關的鎖。

一對多、多對多查詢(查詢條件在關聯對象中時):

  1、在JPA中,一個實體中如果存在多個關聯對象,那麼不能同時eager獲取,只能有一個是eager獲取,其他只能lazy;在Hibernate當中有幾種獨有的解決方法,在JPA當中有2中方法,i.就是前面的改成延時加載;ii.把關聯對象的List改成Set(List允許重複,在多層抓去的時候無法完成映射,Hibernate默認抓去4層,在第三層的時候如果是List就無法完成映射)。

  2、在多對多的查詢中,我們可以使用JPQL,也可以使用原生SQL,同時還可以使用動態查詢,這裏介紹多對多的動態查詢,這裏有一個條件比較苛刻,那就是查詢參數是關聯對象的屬性,一對多類似,多對一可以利用上面介紹的級聯獲取屬性的方式。這裏介紹這種方式的目的是爲了更好的利用以面向對象的方式進行動態查詢。

  舉例:2張表,分別是Employee(id, name)和Company(id, name),二者是多對多的關係,那麼當查詢Employee的時候,條件是更具公司名稱。那麼做法如下:

複製代碼
    @Override
    public List<Employee> findByCompanyName(final String companyName) {
        
        List<Employee> employeeList = employeeRepository.findAll(new Specification<Employee>() {
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//                ListJoin<Employee, Company> companyJoin = root.join(root.getModel().getList("companyList", Company.class), JoinType.LEFT);
                Join<Employee, Company> companyJoin = root.join("companyList", JoinType.LEFT);
                return cb.equal(companyJoin.get("name"), companyName);
            }
        });
        
        return employeeList;
    }
複製代碼

     我們可以使用上面註釋掉的方式,也可以使用下面這種比較簡單的方式。因爲我個人的習慣是儘量不去寫DAO的實現類,除非查詢特別複雜,萬不得已的情況下采用,否則我個人比較偏向於這種方式。

  上面的情況如果更爲極端的話,關聯多個對象,可以按照下面的方式:

複製代碼
    @Override
    public List<Employee> findByCompanyName(final String companyName, final String wage) {
        
        List<Employee> employeeList = employeeRepository.findAll(new Specification<Employee>() {
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//                ListJoin<Employee, Company> companyJoin = root.join(root.getModel().getList("companyList", Company.class), JoinType.LEFT);
                Join<Employee, Company> companyJoin = root.join("companySet", JoinType.LEFT);
                Join<Employee, Wage> wageJoin = root.join("wageSet", JoinType.LEFT);
                Predicate p1 = cb.equal(companyJoin.get("name"), companyName);
                Predicate p2 = cb.equal(wageJoin.get("name"), wage);
//              return cb.and(p1, p2);根據spring-data-jpa的源碼,可以返回一個Predicate,框架內部會自動做query.where(p)的操作,也可以直接在這裏處理,然後返回null,///              也就是下面一段源碼中的實現
                query.where(p1, p2);
                return null;
            }
        });
        
        return employeeList;
    }
複製代碼

  

複製代碼
    /**
     * Applies the given {@link Specification} to the given {@link CriteriaQuery}.
     * 
     * @param spec can be {@literal null}.
     * @param query must not be {@literal null}.
     * @return
     */
    private <S> Root<T> applySpecificationToCriteria(Specification<T> spec, CriteriaQuery<S> query) {

        Assert.notNull(query);
        Root<T> root = query.from(getDomainClass());

        if (spec == null) {
            return root;
        }

        CriteriaBuilder builder = em.getCriteriaBuilder();
        Predicate predicate = spec.toPredicate(root, query, builder);
    // 這裏如果我們重寫的toPredicate方法的返回值predicate不爲空,那麼調用query.where(predicate)
        if (predicate != null) {
            query.where(predicate);
        }

        return root;
    }
複製代碼

 

說明:雖然說JPA中這種方式查詢會存在着多次級聯查詢的問題,對性能有所影響,但是在一般的企業級應用當中,爲了開發的便捷,這種性能犧牲一般來說是可以接受的。

  特別的:在一對多中或者多對一中,即便是fetch爲eager,也會先查詢主對象,再查詢關聯對象,但是在eager的情況下雖然是有多次查詢問題,但是沒有n+1問題,關聯對象不會像n+1那樣多查詢n次,而僅僅是把關聯對象一次性查詢出來,因此,在企業級應用當中,訪問量不大的情況下一般來說沒什麼問題。

  補充一段題外話,關於Hibernate/JPA/Spring-Data-Jpa與MyBatis的區別聯繫,這種話題很多討論,對於Hibernate/JPA/Spring-Data-Jpa,我個人而言基本上能夠熟練使用,談不上精通,對於mybatis,由於深入閱讀過幾次它的源碼,對mybatis的設計思想以及細化到具體的方法,屬性,參數算是比較熟悉,也開發過一些mybatis的相關插件。對於這兩個持久化框架,總體來說的區別是,Hibernate系列的門檻相對較高,配置比較多,相對來說難度要大一些,主要體現在各種關係的問題上,據我所知,很多人的理解其實並不深刻,很多時候甚至配置得有一定的問題,但是優勢也很明顯,SQL自動生成,改數據庫表結構僅僅需要調整幾個註解就行了,在熟練使用的基礎上相對來說要便捷一點。對於mybatis來說,門檻很低,真的很低,低到分分鐘就能入門的程度,我個人最喜歡也是mybatis最吸引人的地方就是靈活,特別的靈活,但是修改數據庫表結構之後需要調整的地方比較多,但是利用目前比較優秀的插件,對於單表操作也基本上能夠達到和Hibernate差不多的境界(會稍微犧牲一點點性能),多表的情況下就要麻煩一點。性能方面的比較,由於我沒做過測試,不太好比較,不過應該mybatis要稍微高一些,畢竟他的查詢SQL可控一些(當然Hibernate也支持原生sql,但是對結果集的處理不夠友好)。

  之後更新:Root對象還有一批fetch方法,這個目前我很少用,後面有時間再來更新。

  補充:單表分頁可以傳入分頁對象,比如findByName(String name, new pageRequest(0, 10));







Spring Data JPA,一種動態條件查詢的寫法

我們在使用SpringData JPA框架時,進行條件查詢,如果是固定條件的查詢,我們可以使用符合框架規則的自定義方法以及@Query註解實現。

如果是查詢條件是動態的,框架也提供了查詢接口。

JpaSpecificationExecutor

 和其他接口使用方式一樣,只需要在你的Dao接口繼承即可(官網代碼)。

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor {
 …
}

JpaSpecificationExecutor提供很多條件查詢方法。

複製代碼
public interface JpaSpecificationExecutor<T> {
    T findOne(Specification<T> var1);

    List<T> findAll(Specification<T> var1);

    Page<T> findAll(Specification<T> var1, Pageable var2);

    List<T> findAll(Specification<T> var1, Sort var2);

    long count(Specification<T> var1);
}
複製代碼

比如方法:

List<T> findAll(Specification<T> var1);

就可以查找出符合條件的所有數據,如果你的框架使用的是前段分頁的技術,那麼這個方法就挺簡便的。

那麼這個方法該如何使用呢?我們看到它需要的參數是一個

org.springframework.data.jpa.domain.Specification

對象。那我們就創建這個對象先看看。

Specification specification = new Specification() {
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
                return null;
            }
        }

IDE自動生成了要重寫的方法toPredicate。

root參數是我們用來對應實體的信息的。criteriaBuilder可以幫助我們製作查詢信息。

複製代碼
/**
 * A root type in the from clause.
 * Query roots always reference entities.
 *
 * @param <X> the entity type referenced by the root
 * @since Java Persistence 2.0
 */
public interface Root<X> extends From<X, X> {...}
複製代碼
複製代碼
/**
 * Used to construct criteria queries, compound selections,
 * expressions, predicates, orderings.
 *
 * <p> Note that <code>Predicate</code> is used instead of <code>Expression&#060;Boolean&#062;</code>
 * in this API in order to work around the fact that Java
 * generics are not compatible with varags.
 *
 * @since Java Persistence 2.0
 */
public interface CriteriaBuilder {...}
複製代碼

CriteriaBuilder對象裏有很多條件方法,比如制定條件:某條數據的創建日期小於今天。

criteriaBuilder.lessThan(root.get("createDate"), today)

該方法返回的對象類型是Predicate。正是toPredicate需要返回的值。

如果有多個條件,我們就可以創建一個Predicate集合,最後用CriteriaBuilder的and和or方法進行組合,得到最後的Predicate對象。

 

複製代碼
/**
     * Create a conjunction of the given restriction predicates.
     * A conjunction of zero predicates is true.
     *
     * @param restrictions zero or more restriction predicates
     *
     * @return and predicate
     */
    Predicate and(Predicate... restrictions);
複製代碼
複製代碼
/**
     * Create a disjunction of the given restriction predicates.
     * A disjunction of zero predicates is false.
     *
     * @param restrictions zero or more restriction predicates
     *
     * @return or predicate
     */
    Predicate or(Predicate... restrictions);
複製代碼

示例:

複製代碼
public List<WeChatGzUserInfoEntity> findByCondition(Date minDate, Date maxDate, String nickname){
        List<WeChatGzUserInfoEntity> resultList = null;
        Specification querySpecifi = new Specification<WeChatGzUserInfoEntity>() {
            @Override
            public Predicate toPredicate(Root<WeChatGzUserInfoEntity> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {

                List<Predicate> predicates = new ArrayList<>();
                if(null != minDate){
                    predicates.add(criteriaBuilder.greaterThan(root.get("subscribeTime"), minDate));
                }
                if(null != maxDate){
                    predicates.add(criteriaBuilder.lessThan(root.get("subscribeTime"), maxDate));
                }
                if(null != nickname){
                    predicates.add(criteriaBuilder.like(root.get("nickname"), "%"+nickname+"%"));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        };
        resultList =  this.weChatGzUserInfoRepository.findAll(querySpecifi);
        return resultList;
    }
複製代碼

and到一起的話所有條件就是且關係,or就是或關係了。

其實也是在Stack Overflow上看到的。








 

jpa動態查詢語句

標籤: jpa動態查詢語句oa系統整理
2015-04-16 10:58  4781人閱讀  評論(0)  收藏  舉報

我們現在在做一個OA系統,將新增的那些數據都寫到數據庫的時候是採用jpa規範的,(不太理解jpa的相關知識點,今天看下相關知識,然後再補充jpa的知識點),現在記錄jpa中的動態查詢語句,其實這些語句都是可以用sql語句寫的,但是sql語句寫得查詢,刪除,插入數據等操作不安全,所以採用jpa的語句。我們的項目是分爲三層結構,第一層是實體層,在該層中專門定義某一實體的相關字段,它的set(),get()方法。第二層是服務層,將service和dao都放在一個組件中,在dao層中定義和數據庫相關的操作方法,在service層中定義相關的業務邏輯層要調用的方法。第三層是restful層,在這層定義的是和前端交互的組件。

首先講講第一層:實體層

定義一個實體

[java]  view plain  copy
  1. /** 
  2.  * 郵件實體 
  3.  * 
  4.  */  
  5. @Entity  
  6. @Table(name = "mail_tbl")  
  7. public class InnerMails implements Serializable {  
  8.   
  9.     private static final long serialVersionUID = 4999674279957375152L;  
  10.   
  11.     @Id  
  12.     @GeneratedValue  
  13.     private long id;  
  14.   
  15.     private String subject;// 主題  
  16.   
  17.     private String toMails;// 收件人 格式 :姓名<userId>;姓名<userId>  
  18.   
  19.     private String urgency;// 緊急程度  
  20.       
  21.     @Column(name = "sendDate")  
  22.     @Temporal(TemporalType.TIMESTAMP)  
  23.     private Date sendDate;// 發佈日期  
  24.   
  25.     private String content;// 郵件內容  
  26.   
  27.     // 原文附件  
  28.     @OneToMany(cascade={ CascadeType.MERGE,CascadeType.REMOVE})  
  29.     @JoinColumn(name = "mail_id")  
  30.     @OrderBy(value = "id DESC")//註釋指明加載Attachment時按id的降序排序    
  31.     private Set<AppendFile> appendFiles=new HashSet<AppendFile>();// 附件  
  32.   
  33.     private String mailUser;// 郵件擁有者 格式:userId  
  34.   
  35.     private String sendMail;// 郵件發送者 格式:姓名<userId>  
  36.   
  37.     private int type;// 狀態標示:-1刪除;0草稿;1發送;2未讀收件,3已讀收件  
  38.   
  39.     public long getId() {  
  40.         return id;  
  41.     }  
  42.   
  43.     public void setId(long id) {  
  44.         this.id = id;  
  45.     }  
  46.   
  47.     public String getSubject() {  
  48.         return subject;  
  49.     }  
  50.   
  51.     public void setSubject(String subject) {  
  52.         this.subject = subject;  
  53.     }  
  54.   
  55.     public String getToMails() {  
  56.         return toMails;  
  57.     }  
  58.   
  59.     public void setToMails(String toMails) {  
  60.         this.toMails = toMails;  
  61.     }  
  62.   
  63.     public String getUrgency() {  
  64.         return urgency;  
  65.     }  
  66.   
  67.     public void setUrgency(String urgency) {  
  68.         this.urgency = urgency;  
  69.     }  
  70.   
  71.     public Date getSendDate() {  
  72.         return sendDate;  
  73.     }  
  74.   
  75.     public void setSendDate(Date sendDate) {  
  76.         this.sendDate = sendDate;  
  77.     }  
  78.   
  79.     public String getContent() {  
  80.         return content;  
  81.     }  
  82.   
  83.     public void setContent(String content) {  
  84.         this.content = content;  
  85.     }  
  86.     public String getMailUser() {  
  87.         return mailUser;  
  88.     }  
  89.   
  90.       
  91.     public void setMailUser(String mailUser) {  
  92.         this.mailUser = mailUser;  
  93.     }  
  94.   
  95.     public Set<AppendFile> getAppendFiles() {  
  96.         return appendFiles;  
  97.     }  
  98.   
  99.     public void setAppendFiles(Set<AppendFile> appendFiles) {  
  100.         this.appendFiles = appendFiles;  
  101.     }  
  102.   
  103.     public String getSendMail() {  
  104.         return sendMail;  
  105.     }  
  106.   
  107.     public void setSendMail(String sendMail) {  
  108.         this.sendMail = sendMail;  
  109.     }  
  110.   
  111.     public int getType() {  
  112.         return type;  
  113.     }  
  114.   
  115.     public void setType(int type) {  
  116.         this.type = type;  
  117.     }  
  118.   
  119. }  
定義查詢實體:

[java]  view plain  copy
  1. package com.gzydt.oa.commons;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.HashMap;  
  5. import java.util.List;  
  6. import java.util.Map;  
  7.   
  8. /** 
  9.  * 分頁查詢參數 
  10.  *  
  11.  * @author huangzhenwei 
  12.  * @since 2014-11-21 
  13.  *  
  14.  */  
  15. public class QueryParam {  
  16.   
  17.     // 排序字段,以「+」、「-」符號連接排序字段名:「+key」表示 按「key」字段升序,「-key」表示按「key」字段降序。  
  18.     private List<String> sorts = new ArrayList<String>();  
  19.     // 起始記錄下標,從0開始計算  
  20.     private int first = 0;  
  21.     // 每頁最大記錄數  
  22.     private int max = 10;  
  23.     // 是否分頁標誌  
  24.     private boolean isPage = true;  
  25.   
  26.     // 查詢參數  
  27.     private Map<String, String> param = new HashMap<String, String>();  
  28.   
  29.     public QueryParam() {  
  30.   
  31.     }  
  32.   
  33.     public int getFirst() {  
  34.         return first;  
  35.     }  
  36.   
  37.     public void setFirst(int first) {  
  38.         this.first = first;  
  39.     }  
  40.   
  41.     public int getMax() {  
  42.         return max;  
  43.     }  
  44.   
  45.     public void setMax(int max) {  
  46.         this.max = max;  
  47.     }  
  48.   
  49.     public Map<String, String> getParam() {  
  50.         return param;  
  51.     }  
  52.   
  53.     public void setParam(Map<String, String> param) {  
  54.         this.param = param;  
  55.     }  
  56.   
  57.     public boolean isPage() {  
  58.         return isPage;  
  59.     }  
  60.   
  61.     public void setPage(boolean isPage) {  
  62.         this.isPage = isPage;  
  63.     }  
  64.   
  65.     public List<String> getSorts() {  
  66.         return sorts;  
  67.     }  
  68.   
  69.     public void setSorts(List<String> sorts) {  
  70.         this.sorts = sorts;  
  71.     }  
  72. }  



第二層:服務層

dao層:定義和數據庫相關操作的方法

[java]  view plain  copy
  1. package com.gzydt.oa.dao;  
  2.   
  3. import java.util.List;  
  4.   
  5. import com.gzydt.oa.commons.QueryParam;  
  6. import com.gzydt.oa.entity.AppendFile;  
  7. import com.gzydt.oa.entity.InnerMails;  
  8.   
  9. /** 
  10.  * 郵件發送dao接口 
  11.  * 
  12.  */  
  13. public interface InnerMailDao {  
  14.     /** 
  15.      * 保存郵件 
  16.      * @param mail 
  17.      * @return 
  18.      */  
  19.     public InnerMails save(InnerMails mail);  
  20.     /** 
  21.      * 更新郵件 
  22.      * @param mail 
  23.      * @return 
  24.      */  
  25.     public InnerMails update(InnerMails mail);  
  26.     /** 
  27.      * 刪除郵件 
  28.      * @param id 
  29.      */  
  30.     public void delete(long id);  
  31.     /** 
  32.      * 查詢郵件 
  33.      * @param queryParam 
  34.      * @return 
  35.      */  
  36.     public List<InnerMails> getlist(QueryParam queryParam);  
  37.     /** 
  38.      * 獲取單個郵件 
  39.      * @param id 
  40.      * @return 
  41.      */  
  42.     public InnerMails get(long id);  
  43.     /** 
  44.      * 獲取滿足條件的郵件的總數 
  45.      * @param queryParam 
  46.      * @return 
  47.      */  
  48.     public int getCount(QueryParam queryParam);  
  49.    /** 
  50.     * 新增附件 
  51.     * @param id 
  52.     * @param appendFile 
  53.     */  
  54.     public void addAttach(long id,AppendFile appendFile);  
  55. }  


[java]  view plain  copy
  1. package com.gzydt.oa.dao.impl;  
  2.   
  3. import java.text.DateFormat;  
  4. import java.text.SimpleDateFormat;  
  5. import java.util.ArrayList;  
  6. import java.util.Date;  
  7. import java.util.HashSet;  
  8. import java.util.Iterator;  
  9. import java.util.List;  
  10. import java.util.Map;  
  11. import java.util.Set;  
  12.   
  13. import javax.persistence.EntityManager;  
  14. import javax.persistence.TypedQuery;  
  15. import javax.persistence.criteria.CriteriaBuilder;  
  16. import javax.persistence.criteria.CriteriaQuery;  
  17. import javax.persistence.criteria.Predicate;  
  18. import javax.persistence.criteria.Root;  
  19.   
  20. import com.gzydt.oa.commons.QueryParam;  
  21. import com.gzydt.oa.dao.InnerMailDao;  
  22. import com.gzydt.oa.entity.AppendFile;  
  23. import com.gzydt.oa.entity.InnerMails;  
  24.   
  25. /** 
  26.  * 郵件實現類 
  27.  */  
  28. public class InnerMailDaoImpl implements InnerMailDao{  
  29.   
  30.     private EntityManager entityManager;  
  31.     public void setEntityManager(EntityManager entityManager) {  
  32.         this.entityManager = entityManager;  
  33.     }  
  34.     /** 
  35.      * 保存郵件 
  36.      * @param mail 
  37.      * @return 
  38.      */  
  39.     @Override  
  40.     public InnerMails save(InnerMails mail) {  
  41.         try {  
  42.             entityManager.persist(mail);  
  43.             entityManager.flush();  
  44.             return mail;  
  45.         } catch ( Exception e ) {  
  46.             e.printStackTrace();  
  47.             return null;  
  48.         }  
  49.     }  
  50.     /** 
  51.      * 更新郵件 
  52.      * @param mail 
  53.      * @return 
  54.      */  
  55.     @Override  
  56.     public InnerMails update(InnerMails mail) {  
  57.         try {  
  58.             entityManager.merge(mail);  
  59.             return mail;  
  60.         } catch ( Exception e ) {  
  61.             e.printStackTrace();  
  62.             return null;  
  63.         }  
  64.     }  
  65.     /** 
  66.      * 刪除郵件 
  67.      * @param id 
  68.      */  
  69.     @Override  
  70.     public void delete(long id) {  
  71.           
  72.             entityManager.createQuery("delete from  PhoneRecord e where e.id=:id").setParameter("id", id).executeUpdate();  
  73.              
  74.           
  75.     }  
  76.     /** 
  77.      * 查詢郵件 
  78.      * @param queryParam 
  79.      * @return 
  80.      */  
  81.     @Override  
  82.     public List<InnerMails> getlist(QueryParam queryParam) {  
  83.         CriteriaBuilder cb = entityManager.getCriteriaBuilder();  
  84.         CriteriaQuery<InnerMails> criteriaQuery = cb.createQuery(InnerMails.class);  
  85.         Root<InnerMails> register = criteriaQuery.from(InnerMails.class);  
  86.         // 過濾條件  
  87.         Predicate[] predicates = createPredicate(queryParam, cb, register);  
  88.         criteriaQuery.where(predicates);  
  89.         int start = queryParam.getFirst();  
  90.         int end = queryParam.getMax();  
  91.         TypedQuery<InnerMails> typedQuery = entityManager.createQuery(criteriaQuery);  
  92.         typedQuery.setFirstResult(start).setMaxResults(end);  
  93.         return typedQuery.getResultList();  
  94.     }  
  95.   //設置查詢條件  
  96.     private Predicate[] createPredicate(QueryParam queryParam, CriteriaBuilder cb, Root<InnerMails> entity) {  
  97.         List<Predicate> predicates=new ArrayList<Predicate>();  
  98.         //取出查詢條件  
  99.         Map<String, String> param= queryParam.getParam();  
  100.         for(Map.Entry entry:param.entrySet()){  
  101.             String key=entry.getKey().toString();  
  102.             String value=entry.getValue().toString();  
  103.             if(key.equals("sendDate")){  
  104.                Predicate conditionTime = createOperateTime(key,cb,value,entity);  
  105.                if(null!=conditionTime){  
  106.                    predicates.add(conditionTime);  
  107.                }         
  108.             }else{  
  109.                 predicates.add(cb.like(entity.<String> get(key),"%"+value+"%"));   
  110.             }  
  111.         }  
  112.         return predicates.toArray(new Predicate[0]);  
  113.     }  
  114.   
  115.     /** 
  116.      * 將時間作爲查詢條件的方法 
  117.      * @param cb 
  118.      * @param value 
  119.      * @param entity 
  120.      */  
  121.     private Predicate createOperateTime(String key,CriteriaBuilder cb, String value, Root<InnerMails> entity) {    
  122.         if(null == value){  
  123.             return null;  
  124.         }  
  125.         String[] operateTime=value.split("~");  
  126.         if(operateTime.length!=2){  
  127.             return null;  
  128.         }  
  129.         try {  
  130.             DateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//格式一定要寫正確,  
  131.             Date t1=df.parse(operateTime[0] + " 00:00:00");  
  132.             Date t2=df.parse(operateTime[1] + " 23:59:59");  
  133.             return cb.between(entity.<Date> get(key), t1, t2);  
  134.         } catch ( Exception e ) {  
  135.            e.printStackTrace();  
  136.         }  
  137.         return null;  
  138.           
  139.     }  
  140.   
  141.     /** 
  142.      * 獲取單個郵件 
  143.      * @param id 
  144.      * @return 
  145.      */  
  146.     @Override  
  147.     public InnerMails get(long id) {  
  148.         InnerMails innersMails=entityManager.find(InnerMails.class, id);  
  149.        Iterator<AppendFile> iterator=innersMails.getAppendFiles().iterator();  
  150.         Set<AppendFile> attachs=new HashSet<AppendFile>();  
  151.         while(iterator.hasNext()){  
  152.             AppendFile appendFile=new AppendFile();  
  153.             appendFile=iterator.next();  
  154.             attachs.add(appendFile);     
  155.         }  
  156.         innersMails.setAppendFiles(attachs);  
  157.        return innersMails;  
  158.     }  
  159.     /** 
  160.      * 獲取滿足條件的郵件的總數 
  161.      * @param queryParam 
  162.      * @return 
  163.      */  
  164.     @Override  
  165.     public int getCount(QueryParam queryParam) {  
  166.         CriteriaBuilder cb = entityManager.getCriteriaBuilder();  
  167.         CriteriaQuery<Long> criteriaQuery = cb.createQuery(Long.class);  
  168.         Root<InnerMails> mails = criteriaQuery.from(InnerMails.class);  
  169.         criteriaQuery.select(cb.countDistinct(mails));  
  170.         // 過濾條件  
  171.         Predicate[] predeicates = createPredicate(queryParam, cb, mails);  
  172.         criteriaQuery.where(predeicates);  
  173.         TypedQuery<Long> typedQuery = entityManager.createQuery(criteriaQuery);  
  174.         int count = 0;  
  175.         try {  
  176.             count = typedQuery.getSingleResult().intValue();  
  177.         } catch ( Exception e ) {  
  178.             e.printStackTrace();  
  179.         }  
  180.         return count;  
  181.     }  
  182.     /** 
  183.      * 新增附件 
  184.      * @param id 
  185.      * @param appendFile 
  186.      */  
  187.     @Override  
  188.     public void addAttach(long id, AppendFile appendFile) {  
  189.         InnerMails entity=this.get(id);  
  190.         entity.getAppendFiles().add(appendFile);  
  191.         entityManager.merge(entity);  
  192.           
  193.     }  
  194.   
  195. }  
動態查詢語句的相關知識

1:查詢User表中的字段adminlever的小於給定值的數據

第一種寫法:(安全,推薦使用這種)

[java]  view plain  copy
  1. /** 
  2.     * 查詢某一級別以上的用戶 
  3.     */  
  4.    @Override  
  5.    public List<User> getOnLeverUper(int lever) {  
  6.      CriteriaBuilder cb =entityManager.getCriteriaBuilder();  
  7.      CriteriaQuery<User> criterQuery=cb.createQuery(User.class);  
  8.      Root<User> entity=criterQuery.from(User.class);  
  9.      Path<Integer> adminLever=entity.<Integer> get("adminlever") ;   
  10.      criterQuery.where(cb.lessThan(adminLever, lever));  
  11.      TypedQuery<User> typedQuery=entityManager.createQuery(criterQuery);  
  12.      return typedQuery.getResultList();  
  13.     
  14.    }  
第二種寫法:(不太安全,)

[java]  view plain  copy
  1. /** 
  2.     * 查詢某一級別以上的用戶 
  3.     */  
  4.    @Override  
  5.    public List<User> getOnLeverUper(int lever) {  
  6.        List<User> users=entityManager.createQuery("from User u where u.adminlever<:adminlever")  
  7.                .setParameter("adminlever",lever).getResultList();  
  8.         return users;    
  9.     
  10.    }  
第二種刪除數據(有時候會由於某實體和另一實體設置了一對一或者多對一,或者多對多的關係而導致不能正常刪除數據),解決方法:

[java]  view plain  copy
  1. /** 
  2.      * 刪除登記信息 
  3.      *  
  4.      * @param id 
  5.      *            登記編號 
  6.      */  
  7.     @Override  
  8.     public void handleDelete(long id) throws Exception {  
  9.         ReceiptEntity entity = entityManager.find(ReceiptEntity.class, id);  
  10.         entityManager.remove(entity);  
  11.         //下面的的方法刪除應爲存在外鍵關聯會刪除失敗  
  12.        /*entityManager.createQuery("delete from  ReceiptEntity e where e.id=:id"). 
  13.         setParameter("id", id).executeUpdate();*/  
  14.     }  
service層:接口

[java]  view plain  copy
  1. package com.gzydt.oa.service;  
  2.   
  3. import java.util.List;  
  4.   
  5. import com.gzydt.oa.commons.QueryParam;  
  6. import com.gzydt.oa.entity.AppendFile;  
  7. import com.gzydt.oa.entity.InnerMails;  
  8.   
  9. /** 
  10.  * 內部郵件接口 
  11.  * 
  12.  */  
  13. public interface InnerMailService {  
  14.     /** 
  15.      * 保存郵件 
  16.      * @param mail 
  17.      * @return 
  18.      */  
  19.     public InnerMails save(InnerMails mail);  
  20.     /** 
  21.      * 更新郵件 
  22.      * @param mail 
  23.      * @return 
  24.      */  
  25.     public InnerMails update(InnerMails mail);  
  26.     /** 
  27.      * 刪除郵件 
  28.      * @param id 
  29.      */  
  30.     public void delete(long id);  
  31.     /** 
  32.      * 查詢郵件 
  33.      * @param queryParam 
  34.      * @return 
  35.      */  
  36.     public List<InnerMails> getlist(QueryParam queryParam);  
  37.     /** 
  38.      * 獲取單個郵件 
  39.      * @param id 
  40.      * @return 
  41.      */  
  42.     public InnerMails get(long id);  
  43.     /** 
  44.      * 獲取滿足條件的郵件的總數 
  45.      * @param queryParam 
  46.      * @return 
  47.      */  
  48.     public int getCount(QueryParam queryParam);  
  49.     /** 
  50.      * 發郵件 
  51.      * @param content 
  52.      *//* 
  53.     public void sendMail(String content);*/  
  54.     /** 
  55.      * 新增附件 
  56.      * @param id 
  57.      * @param appendFile 
  58.      */  
  59.     public void addAttach(long id,AppendFile appendFile);  
  60.   
  61. }  
service層:實現類
[java]  view plain  copy
  1. package com.gzydt.oa.service.impl;  
  2.   
  3. import java.util.List;  
  4.   
  5. import com.gzydt.oa.commons.QueryParam;  
  6. import com.gzydt.oa.dao.InnerMailDao;  
  7. import com.gzydt.oa.entity.AppendFile;  
  8. import com.gzydt.oa.entity.InnerMails;  
  9. import com.gzydt.oa.service.InnerMailService;  
  10.   
  11. /** 
  12.  * 內部郵件服務類 
  13.  * 
  14.  */  
  15. public class InnerMailServiceImpl implements InnerMailService{  
  16.   
  17.     private InnerMailDao mailDao;  
  18.       
  19.     public void setMailDao(InnerMailDao mailDao) {  
  20.         this.mailDao = mailDao;  
  21.     }  
  22.     /** 
  23.      * 保存郵件 
  24.      * @param mail 
  25.      * @return 
  26.      */  
  27.     @Override  
  28.     public InnerMails save(InnerMails mail) {  
  29.         return mailDao.save(mail);  
  30.     }  
  31.   
  32.     @Override  
  33.     public InnerMails update(InnerMails mail) {  
  34.         return mailDao.update(mail);  
  35.     }  
  36.     /** 
  37.      * 刪除郵件 
  38.      * @param id 
  39.      */  
  40.     @Override  
  41.     public void delete(long id) {  
  42.        mailDao.delete(id);  
  43.           
  44.     }  
  45.     /** 
  46.      * 查詢郵件 
  47.      * @param queryParam 
  48.      * @return 
  49.      */  
  50.     @Override  
  51.     public List<InnerMails> getlist(QueryParam queryParam) {  
  52.        return mailDao.getlist(queryParam);  
  53.     }  
  54.     /** 
  55.      * 獲取單個郵件 
  56.      * @param id 
  57.      * @return 
  58.      */  
  59.     @Override  
  60.     public InnerMails get(long id) {  
  61.        return mailDao.get(id);  
  62.     }  
  63.     /** 
  64.      * 獲取滿足條件的郵件的總數 
  65.      * @param queryParam 
  66.      * @return 
  67.      */  
  68.     @Override  
  69.     public int getCount(QueryParam queryParam) {  
  70.         return mailDao.getCount(queryParam);  
  71.     }  
  72.   
  73.    /* @Override 
  74.     public void sendMail(String content) { 
  75.         
  76.          
  77.     }*/  
  78.     /** 
  79.      * 新增附件 
  80.      * @param id 
  81.      * @param appendFile 
  82.      */  
  83.     @Override  
  84.     public void addAttach(long id, AppendFile appendFile) {  
  85.        mailDao.addAttach(id, appendFile);  
  86.           
  87.     }  
  88.   
  89. }  
在服務層中定義相關的服務配置

[java]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <blueprint default-activation="eager"  
  3.     xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xmlns:jpa="http://aries.apache.org/xmlns/jpa/v1.0.0" xmlns:tx="http://aries.apache.org/xmlns/transactions/v1.0.0"  
  5.     xsi:schemaLocation="  
  6.       http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd  
  7.       http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd  
  8.       http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd">  
  9.   
  10.     <!-- This gets the container-managed EntityManager and injects it into the   
  11.         ServiceImpl bean. -->  
  12.   
  13.     <!-- dao -->  
  14.     <bean id="mailDao" class="com.gzydt.oa.dao.impl.InnerMailDaoImpl">  
  15.         <jpa:context unitname="com.gzydt.jpa.persistence"  
  16.             property="entityManager" />  
  17.         <tx:transaction method="*" value="Required" />  
  18.     </bean>  
  19.   
  20.     <!--新增結束 -->  
  21.   
  22.   
  23.     <!-- bean -->  
  24.       
  25.     <bean id="mailService" class="com.gzydt.oa.service.impl.InnerMailServiceImpl">  
  26.         <property name="mailDao" ref="mailDao" />  
  27.     </bean>  
  28.   
  29.        
  30.         <!--新增結束 -->  
  31.   
  32.     <!-- service -->  
  33.     <service ref="mailService" interface="com.gzydt.oa.service.InnerMailService" />  
  34.   
  35.      <!-- This bundle makes use of Karaf commands to demonstrate core persistence   
  36.         operations. Feel free to remove it. -->  
  37.     <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">  
  38.         <command name="msg/add">  
  39.             <action class="com.gzydt.oa.command.AddMessage">  
  40.                 <property name="messageService" ref="messageService" />  
  41.             </action>  
  42.         </command>  
  43.         <command name="msg/list">  
  44.             <action class="com.gzydt.oa.command.GetMessage">  
  45.                 <property name="messageService" ref="messageService" />  
  46.             </action>  
  47.         </command>  
  48.         <command name="msg/delete">  
  49.             <action class="com.gzydt.oa.command.DeleteMessage">  
  50.                 <property name="messageService" ref="messageService" />  
  51.             </action>  
  52.         </command>  
  53.   
  54.         <command name="dept/add">  
  55.             <action class="com.gzydt.oa.command.DeptAddCommand">  
  56.                 <property name="deptService" ref="deptService" />  
  57.             </action>  
  58.         </command>  
  59.         <command name="dept/list">  
  60.             <action class="com.gzydt.oa.command.DeptGetCommand">  
  61.                 <property name="deptService" ref="deptService" />  
  62.             </action>  
  63.         </command>  
  64.         <command name="dept/delete">  
  65.             <action class="com.gzydt.oa.command.DeptDeleteCommand">  
  66.                 <property name="deptService" ref="deptService" />  
  67.             </action>  
  68.         </command>  
  69.     </command-bundle>  
  70.   
  71. </blueprint>  

第三層:restful層

[java]  view plain  copy
  1. package com.gzydt.oa.resource;  
  2.   
  3. import java.text.ParseException;  
  4. import java.util.List;  
  5.   
  6. import javax.ws.rs.Consumes;  
  7. import javax.ws.rs.DELETE;  
  8. import javax.ws.rs.GET;  
  9. import javax.ws.rs.HeaderParam;  
  10. import javax.ws.rs.POST;  
  11. import javax.ws.rs.PUT;  
  12. import javax.ws.rs.Path;  
  13. import javax.ws.rs.PathParam;  
  14. import javax.ws.rs.Produces;  
  15. import javax.ws.rs.QueryParam;  
  16. import javax.ws.rs.core.MediaType;  
  17. import javax.ws.rs.core.Response;  
  18.   
  19. import org.apache.cxf.jaxrs.ext.multipart.Attachment;  
  20. import org.apache.cxf.jaxrs.ext.multipart.Multipart;  
  21.   
  22. /** 
  23.  * 內部郵件的restful 
  24.  */  
  25. @Path("/mails")  
  26. public interface InnerMailsResource {  
  27.     /** 
  28.      * 新增郵件 
  29.      * @param content 
  30.      * @return 
  31.      */  
  32.     @POST  
  33.     @Path("/")  
  34.     @Consumes("multipart/form-data")  
  35.     public Response save(@Multipart("content") String content,  
  36. List<Attachment> attachments) throws ParseException ;  
  37.     /** 
  38.      * 更新郵件 
  39.      * @param id 
  40.      * @param content 
  41.      * @return 
  42.      */  
  43.     @PUT  
  44.     @Path("/{id}")  
  45.     @Consumes("multipart/form-data")  
  46.     public Response update(@PathParam("id"long id,@Multipart("content") String content,  
  47. List<Attachment> attachments) throws ParseException;  
  48.     /** 
  49.      * 查詢郵件 
  50.      * @param id 
  51.      * @return 
  52.      */  
  53.     @GET  
  54.     @Path("/{id}")  
  55.     public Response get(@PathParam("id"long id);  
  56.     /** 
  57.      * 查詢郵件列表 
  58.      * @param range 
  59.      * @param query 
  60.      * @return 
  61.      */  
  62.     @GET  
  63.     @Path("/list")  
  64.     public Response getList(@HeaderParam("range") String range,@QueryParam("query") String query);  
  65.     /** 
  66.      * 刪除郵件 
  67.      * @param id 
  68.      * @return 
  69.      */  
  70.     @DELETE  
  71.     @Path("/{id}")  
  72.     public Response delete(@PathParam("id"long id);  
  73.     /** 
  74.      * 發送郵件 
  75.      * @param content 
  76.      * @return  數據格式:{"data":{},"toMail":""} 
  77.      */  
  78.     @POST  
  79.     @Path("/sendMails/{id}")  
  80.     public Response sendMail(@PathParam("id")long id) ;  
  81.     /** 
  82.      * 下載附件 
  83.      * @param id(附件的id) 
  84.      * @return 
  85.      */  
  86.     @GET  
  87.     @Path("/getAttach/{id}")  
  88.     @Produces(MediaType.APPLICATION_OCTET_STREAM)  
  89.     public Response downLoadAttach(@PathParam("id"long id);  
  90.       
  91.       
  92.   
  93. }  

實現類:

[java]  view plain  copy
  1. package com.gzydt.oa.resource.impl;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7. import java.io.InputStream;  
  8. import java.io.OutputStream;  
  9. import java.text.ParseException;  
  10. import java.text.SimpleDateFormat;  
  11. import java.util.Date;  
  12. import java.util.HashSet;  
  13. import java.util.List;  
  14. import java.util.Set;  
  15. import java.util.regex.Matcher;  
  16. import java.util.regex.Pattern;  
  17.   
  18. import javax.activation.DataHandler;  
  19. import javax.ws.rs.WebApplicationException;  
  20. import javax.ws.rs.core.MediaType;  
  21. import javax.ws.rs.core.Response;  
  22. import javax.ws.rs.core.Response.Status;  
  23. import javax.ws.rs.core.StreamingOutput;  
  24.   
  25. import net.sf.json.JSONArray;  
  26. import net.sf.json.JSONObject;  
  27. import net.sf.json.JsonConfig;  
  28.   
  29. import org.apache.cxf.jaxrs.ext.multipart.Attachment;  
  30. /*import org.json.JSONArray;*/  
  31.   
  32. import com.gzydt.oa.commons.QueryParam;  
  33. import com.gzydt.oa.entity.AppendFile;  
  34. import com.gzydt.oa.entity.InnerMails;  
  35. import com.gzydt.oa.resource.InnerMailsResource;  
  36. import com.gzydt.oa.service.AppendFileService;  
  37. import com.gzydt.oa.service.InnerMailService;  
  38. import com.gzydt.oa.util.Constant;  
  39. import com.gzydt.oa.util.QueryUtil;  
  40.   
  41. public class InnerMailsResourceImpl implements InnerMailsResource {  
  42.     private InnerMailService emailService;  
  43.   
  44.     public void setEmailService(InnerMailService emailService) {  
  45.         this.emailService = emailService;  
  46.     }  
  47.   
  48.     private AppendFileService appendFileService;  
  49.   
  50.     public void setAppendFileService(AppendFileService appendFileService) {  
  51.         this.appendFileService = appendFileService;  
  52.     }  
  53.   
  54.     private static final String PATH = "data/oa/upload/mails";  
  55.   
  56.     @Override  
  57.     public Response save(String content, List<Attachment> attachments) throws ParseException {  
  58.         //去掉懶加載字段  
  59.         JsonConfig jsonConfig = Constant.jsonDateConfig;  
  60.         jsonConfig.setExcludes(new String[] { "appendFiles"});  
  61.         JSONObject preceInfo = JSONObject.fromObject(content);  
  62.         JSONObject backInfo = new JSONObject();  
  63.         InnerMails entity = new InnerMails();  
  64.         Date sendDate = null;  
  65.         if ( preceInfo.optString("sendDate") != null && preceInfo.optString("sendDate") != "" ) {  
  66.         //這裏的MM必須是要大寫,若是寫爲mm,則是分鐘,,格式一定要按照正規的來寫<span class="con">yyyy-MM-dd HH:mm:ss</span>,  
  67. //該大寫就大寫,小寫就小寫,並且中間有空格等,都不能錯誤。不然時間會出錯  
  68.         SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");  
  69.             sendDate = df.parse(preceInfo.optString("sendDate"));  
  70.         }  
  71.         preceInfo.put("sendDate", sendDate);  
  72.         entity = (InnerMails) JSONObject.toBean(preceInfo, InnerMails.class);  
  73.          
  74.         if ( !preceInfo.has("type") ) {  
  75.             entity.setType(0);  
  76.         }  
  77.         entity = emailService.save(entity);  
  78.      // 新增附件到附件表中  
  79.         Set<AppendFile> appfiles=addAttach(attachments, entity);  
  80.         entity.setAppendFiles(appfiles);  
  81.         entity=emailService.update(entity);  
  82.   
  83.         if ( null != entity ) {  
  84.             backInfo = JSONObject.fromObject(entity);  
  85.             return Response.ok(backInfo.toString(), MediaType.APPLICATION_JSON).build();  
  86.         }  
  87.         backInfo.put("message""保存失敗");  
  88.         return Response.ok(backInfo.toString(), MediaType.APPLICATION_JSON).build();  
  89.   
  90.     }  
  91.       
  92.     // 保存並關聯附件  
  93.     private  Set<AppendFile>  addAttach(List<Attachment> attachments,InnerMails entity){  
  94.         Set<AppendFile> appenFiles=new HashSet<AppendFile>();  
  95.         for (Attachment attachment : attachments) {  
  96.             if (attachment.getContentType().toString().startsWith("application/octet-stream")) {  
  97.                 DataHandler dh = attachment.getDataHandler();  
  98.                 long time = new Date().getTime();  
  99.                 String fileName = null;  
  100.                 try {  
  101.                     fileName = new String(dh.getName().getBytes("ISO-8859-1"), "UTF-8");  
  102.                     writeFile(dh, fileName);  
  103.                 } catch (Exception e) {  
  104.                     e.printStackTrace();  
  105.                      
  106.                 }  
  107.                 AppendFile file = new AppendFile();  
  108.                 file.setSerialNumber(time);// 唯一標識  
  109.                 file.setFileName(fileName);// 文件名  
  110.                 file.setExtension(fileName.substring(fileName.lastIndexOf(".") + 1));// 文件後綴    
  111.                 file.setType("email");// 文件類型  
  112.                 emailService.addAttach(entity.getId(), file);  
  113.                 AppendFile result = null;  
  114.                 result = appendFileService.getByNumber(time);  
  115.                 appenFiles.add(result);  
  116.                   
  117.             }  
  118.         }  
  119.         return appenFiles;  
  120.     }  
  121.      
  122.   
  123.     // 寫文件  
  124.     private void writeFile(DataHandler dh, String fileName) throws IOException {  
  125.         InputStream is = dh.getInputStream();  
  126.         File file = new File(PATH);  
  127.         if ( !file.exists() ) {  
  128.             file.mkdirs();  
  129.         }  
  130.         // LOG.info("附件目錄:" + file.getAbsolutePath());  
  131.         writeToFile(is, PATH + fileName);  
  132.     }  
  133.   
  134.     private void writeToFile(InputStream is, String path) throws IOException {  
  135.   
  136.         File file = new File(path);  
  137.         OutputStream out = new FileOutputStream(file);  
  138.         int len = 0;  
  139.         byte[] bytes = new byte[1024];  
  140.         while ( (len = is.read(bytes)) != -1 ) {  
  141.             out.write(bytes, 0, len);  
  142.         }  
  143.         out.flush();  
  144.         out.close();  
  145.   
  146.     }  
  147.   
  148.     @Override  
  149.     public Response update(long id, String content, List<Attachment> attachments) throws ParseException {  
  150.         InnerMails entity = emailService.get(id);  
  151.         JSONObject preceInfo = JSONObject.fromObject(content);  
  152.         JSONObject backInfo = new JSONObject();  
  153.         if ( null != entity ) {  
  154.             entity.setSubject(preceInfo.optString("subject"));  
  155.             entity.setToMails(preceInfo.optString("toMails"));  
  156.             entity.setUrgency(preceInfo.optString("urgency"));  
  157.             Date sendDate = null;  
  158.             if ( preceInfo.optString("sendDate") != null && preceInfo.optString("sendDate") != "" ) {  
  159.                 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");  
  160.                 sendDate = df.parse(preceInfo.optString("sendDate"));  
  161.             }  
  162.           //保存附件  
  163.             Set<AppendFile> appfiles=addAttach(attachments, entity);  
  164.             entity.setAppendFiles(appfiles);  
  165.             entity.setSendDate(sendDate);  
  166.             entity.setContent(preceInfo.optString("content"));  
  167.             entity.setMailUser(preceInfo.optString("mailUser"));  
  168.             entity.setSendMail(preceInfo.optString("sendMail"));  
  169.             entity.setType(preceInfo.optInt("type"));  
  170.             
  171.             addAttach(attachments, entity);  
  172.             entity = emailService.update(entity);  
  173.             if ( entity != null ) {  
  174.                 backInfo = JSONObject.fromObject(entity);  
  175.                 return Response.ok(backInfo.toString(), MediaType.APPLICATION_JSON).build();  
  176.             } else {  
  177.                 backInfo.put("message""修改失敗");  
  178.                 return Response.ok(backInfo.toString(), MediaType.APPLICATION_JSON).build();  
  179.             }  
  180.   
  181.         }  
  182.         backInfo.put("message""沒有找到指定的郵件");  
  183.         return Response.ok(backInfo.toString(), MediaType.APPLICATION_JSON).build();  
  184.     }  
  185.   
  186.     @Override  
  187.     public Response get(long id) {  
  188.         JSONObject backInfo = new JSONObject();  
  189.         InnerMails entity = emailService.get(id);  
  190.         JSONObject jo;  
  191.         /*JsonConfig JSONConfig = Constant.jsonDateConfigWithHour; 
  192.         JSONConfig.setExcludes(new String[] {"appendFiles"});*/  
  193.         // 去掉延遲加載的字段  
  194.         jo = JSONObject.fromObject(entity);  
  195.         //修改狀態爲已讀  
  196.         entity.setType(3);  
  197.         emailService.update(entity);  
  198.         if ( null != entity ) {  
  199.             backInfo = JSONObject.fromObject(jo);  
  200.             return Response.ok(backInfo.toString(), MediaType.APPLICATION_JSON).build();  
  201.         }  
  202.         backInfo.put("message""沒有找到指定的內部郵件");  
  203.         return Response.ok(backInfo.toString(), MediaType.APPLICATION_JSON).build();  
  204.     }  
  205.   
  206.     @Override  
  207.     public Response getList(String range, String query) {  
  208.         QueryParam queryParam = new QueryParam();  
  209.         int from = 0;  
  210.         int to = 9;  
  211.         try {  
  212.             
  213.             String[] ranges = range.replace("items=",  "").split("-");  
  214.             from = Integer.parseInt(ranges[0]);  
  215.             to = Integer.parseInt(ranges[1]);  
  216.         } catch ( Exception e ) {  
  217.             e.printStackTrace();  
  218.         }  
  219.   
  220.         queryParam.setFirst(from);  
  221.         int max = to - from + 1;  
  222.         if ( max > 0 ) {  
  223.             queryParam.setMax(max);  
  224.         }  
  225.         if(null!=query){  
  226.             QueryUtil.prepareQuery(query, queryParam);  
  227.         }  
  228.         int count=emailService.getCount(queryParam);  
  229.         List<InnerMails> list=emailService.getlist(queryParam);  
  230.        JsonConfig jsonconfig=Constant.jsonDateConfig;  
  231.         jsonconfig.setExcludes(new String[] {"appendFiles"});  
  232.         String contentRange=String.format("items %d-%d/%d", from,to,count);  
  233.         JSONArray ja = JSONArray.fromObject(list,jsonconfig);  
  234.         String entity = ja.toString();  
  235.         return Response.ok(entity, MediaType.APPLICATION_JSON).header("Content-Range", contentRange).build();  
  236.   
  237.     }  
  238.   
  239.     @Override  
  240.     public Response delete(long id) {  
  241.        JSONObject backInfo=new JSONObject();  
  242.        try {  
  243.         emailService.delete(id);  
  244.         backInfo.put("message""刪除成功");  
  245.         return Response.ok(backInfo.toString(), MediaType.APPLICATION_JSON).build();  
  246.     } catch ( Exception e ) {  
  247.         backInfo.put("message","刪除失敗");  
  248.         return Response.ok(backInfo.toString(),MediaType.APPLICATION_JSON).build();  
  249.     }  
  250.     }  
  251.   
  252.     @Override  
  253.     public Response sendMail(/*String content,List<Attachment> attachments*/long id){  
  254.        JSONObject backInfo=new JSONObject();  
  255.         //通過id找到對應的郵件  
  256.         InnerMails entity=emailService.get(id);  
  257.         //將A的郵件mail狀態改爲發送  
  258.         entity.setType(1);  
  259.         entity=emailService.update(entity);  
  260.         //找到收件人,根據收件人的個數來新增多條郵件  
  261.         String toMail=entity.getToMails();  
  262.         String[] toMails=toMail.split(",");  
  263.           
  264.         for(String tomail:toMails){  
  265.             //新增郵件,修改mail1的擁有者,修改狀態爲未讀  
  266.             InnerMails newMails=new InnerMails();  
  267.             newMails.setSubject(entity.getSubject());  
  268.             newMails.setToMails(entity.getToMails());  
  269.             newMails.setUrgency(entity.getUrgency());  
  270.             newMails.setAppendFiles(entity.getAppendFiles());  
  271.             newMails.setSendDate(entity.getSendDate());  
  272.             newMails.setContent(entity.getContent());  
  273.             newMails.setSendMail(entity.getSendMail());  
  274.             newMails.setType(2);  
  275.             newMails.setMailUser(getNoFromChar(tomail));  
  276.             emailService.save(newMails);  
  277.         }  
  278.           
  279.         backInfo.put("發送郵件的人數", toMails.length);  
  280.         return Response.ok(backInfo.toString(), MediaType.APPLICATION_JSON).build();  
  281.           
  282.           
  283.     }  
  284.     //截取字符串中的數字  
  285.      private String  getNoFromChar(String params) {   
  286.         String regex="[^0-9]";  
  287.         Pattern p=Pattern.compile(regex);  
  288.         Matcher m=p.matcher(params);  
  289.         return m.replaceAll("").trim();  
  290.     }  
  291.   
  292.     @Override  
  293.     public Response downLoadAttach(long id) {  
  294.        //根據附件名稱去指定路徑中找附件  
  295.         AppendFile appendFile=appendFileService.get(id);  
  296.         if ( null == appendFile ) {  
  297.             return Response.status(Status.NOT_FOUND).entity("找不到文件").build();  
  298.         }  
  299.         final File file=new File(PATH, appendFile.getFileName());  
  300.         JSONObject preceInfo=new JSONObject();  
  301.         if(!file.exists()||!file.isFile()){  
  302.             preceInfo.put("message","沒有找到指定的文件");  
  303.             return Response.status(Status.NOT_FOUND).entity("找不到文件:"+file.getName()).build();  
  304.         }  
  305.         //下載附件  
  306.         StreamingOutput entity=downLoad(file);  
  307.         String fileName=file.getName().toLowerCase();  
  308.         String type=MediaType.APPLICATION_OCTET_STREAM;  
  309.         if(fileName.endsWith(".jpg")||fileName.endsWith(".png")){  
  310.             type="image/jpeg";  
  311.         }else if(fileName.endsWith(".doc")){  
  312.             type="application/msword;charset=utf-8";  
  313.         }else if(fileName.endsWith(".pdf")){  
  314.             type="application/pdf;charset=utf-8";  
  315.         }  
  316.         try {  
  317.             //結局中文名字亂碼的問題  
  318.             fileName=new String(file.getName().getBytes("UTF-8"),"ISO-8859-1");  
  319.         } catch ( Exception e ) {  
  320.             // TODO: handle exception  
  321.         }  
  322.         return Response.ok(entity, type).header("Content-disposition""inline;filename="+fileName).build();  
  323.     }  
  324.   
  325.     //下載附件方法  
  326.     private StreamingOutput downLoad(final File file) {  
  327.         StreamingOutput entity=new StreamingOutput() {  
  328.               
  329.             @Override  
  330.             public void write(OutputStream output) throws IOException, WebApplicationException {  
  331.                 int len=0;  
  332.                 byte[] buffer=new byte[1024];  
  333.                 InputStream intpStream=new FileInputStream(file);  
  334.                 while((len = intpStream.read(buffer))>0){  
  335.                     output.write(buffer, 0,len);  
  336.                 }  
  337.                 intpStream.close();  
  338.                 output.flush();  
  339.                 output.close();  
  340.                   
  341.             }  
  342.         };  
  343.         return entity;  
  344.     }  
  345. }  
restful層的配置文件:

[java]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2.   
  3. <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"  
  5.     xmlns:cxf="http://cxf.apache.org/blueprint/core"  
  6.     xsi:schemaLocation="  
  7.       http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd  
  8.       http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd  
  9.       http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd">  
  10.   
  11.     <jaxrs:server id="OARestService" address="/oa">  
  12.         <jaxrs:serviceBeans>  
  13.             <ref component-id="mailRestService" />  
  14.         </jaxrs:serviceBeans>  
  15.         <jaxrs:providers>  
  16.             <ref component-id="authFilter" />  
  17.         </jaxrs:providers>  
  18.     </jaxrs:server>  
  19.   
  20.     <!-- implements OAuthDataProvider -->  
  21.     <bean id="oauthProvider" class="com.gzydt.oa.auth.OAuthManager" />  
  22.     <bean id="authorizationService"  
  23.         class="org.apache.cxf.rs.security.oauth2.services.AuthorizationCodeGrantService">  
  24.         <property name="dataProvider" ref="oauthProvider" />  
  25.     </bean>  
  26.     <jaxrs:server id="appServer" address="/myapp">  
  27.         <jaxrs:serviceBeans>  
  28.             <ref component-id="authorizationService" />  
  29.         </jaxrs:serviceBeans>  
  30.     </jaxrs:server>  
  31.   
  32.     <!-- <cxf:bus> <cxf:features> <cxf:logging /> </cxf:features> </cxf:bus> -->  
  33.   
  34.   
  35.     <!-- We are using the OSGi Blueprint XML syntax to define a bean that we   
  36.         referred to in our JAX-RS server setup. This bean carries a set of JAX-RS   
  37.         annotations that allow its methods to be mapped to incoming requests. -->  
  38.     <bean id="authRestService" class="com.gzydt.oa.resource.impl.AuthResourceImpl">  
  39.         <property name="userService" ref="userService" />  
  40.     </bean>  
  41.     <bean id="authFilter" class="com.gzydt.oa.auth.AuthenticationFilter">  
  42.     </bean>  
  43.     <bean id="backlogRestService" class="com.gzydt.oa.resource.impl.BacklogResourceImpl">  
  44.         <property name="registerService" ref="registerService" />  
  45.     </bean>  
  46.     <bean id="securityResource" class="com.gzydt.oa.resource.impl.SecurityResourceImpl"  
  47.         scope="singleton" init-method="init" destroy-method="destroy">  
  48.         <property name="userService" ref="userService" />  
  49.         <property name="deptService" ref="deptService" />  
  50.         <property name="dutyUsersService" ref="dutyUsersService" />  
  51.         <property name="unitService" ref="unitService" />  
  52.   
  53.     </bean>  
  54.   
  55.     <!--添加bean -->  
  56.   
  57.     <bean id="mailRestService" class="com.gzydt.oa.resource.impl.InnerMailsResourceImpl">  
  58.         <property name="emailService" ref="emailService" />  
  59.         <property name="appendFileService" ref="appendFileService" />  
  60.     </bean>  
  61.   
  62.     <!--添加bean結束 -->  
  63.       
  64.     <reference id="emailService" interface="com.gzydt.oa.service.InnerMailService" />  
  65.       
  66.     <!--添加reference結束 -->  
  67.   
  68. </blueprint>  

解析前端傳來的參數:

[java]  view plain  copy
  1. package com.gzydt.oa.util;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.HashMap;  
  5. import java.util.Iterator;  
  6. import java.util.List;  
  7. import java.util.Map;  
  8.   
  9. import net.sf.json.JSONObject;  
  10.   
  11. import com.gzydt.oa.commons.QueryParam;  
  12.   
  13. public class QueryUtil {  
  14.   
  15.     /** 
  16.      * 解析url中的查詢條件的參數 
  17.      *  
  18.      * @param query 
  19.      *            :查詢標示 
  20.      * @param queryParam 
  21.      *            :url中的查詢參數 
  22.      * @return 爲空 
  23.      */  
  24.     public static void prepareQuery(String query, QueryParam queryParam) {  
  25.         try {  
  26.             JSONObject jo = JSONObject.fromObject(query);  
  27.             Map<String, String> param = new HashMap<String, String>();  
  28.             List<String> sorts = new ArrayList<String>();  
  29.             for ( @SuppressWarnings("unchecked")  
  30.             Iterator<String> iterator = jo.keySet().iterator(); iterator.hasNext(); ) {  
  31.                 String key = iterator.next();  
  32.                 String value = jo.optString(key);  
  33.                 if ( !value.isEmpty() ) {  
  34.                     if ( "sort".equals(key) ) {  
  35.                         for ( String s : value.split(",") ) {  
  36.                             if ( null != s ) {  
  37.                                 if ( s.startsWith("8") ) {// 前端無法傳「+」  
  38.                                     s = "+" + s.substring(1, s.length());  
  39.                                 } else {  
  40.                                     s = "-" + s.substring(1, s.length());  
  41.                                 }  
  42.                                 sorts.add(s);  
  43.                             }  
  44.                         }  
  45.                     } else {  
  46.                         param.put(key, value);  
  47.                     }  
  48.                 }  
  49.             }  
  50.             queryParam.setParam(param);  
  51.             queryParam.setSorts(sorts);  
  52.         } catch ( Exception e ) {  
  53.             e.printStackTrace();  
  54.         }  
  55.     }  
  56.   
  57. }  

內部郵件的測試類:

[java]  view plain  copy
  1. package com.gzydt.oa.resource;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileNotFoundException;  
  5. import java.io.IOException;  
  6. import java.io.UnsupportedEncodingException;  
  7. import java.util.ArrayList;  
  8. import java.util.List;  
  9.   
  10. import org.apache.commons.httpclient.HttpClient;  
  11. import org.apache.commons.httpclient.HttpException;  
  12. import org.apache.commons.httpclient.NameValuePair;  
  13. import org.apache.commons.httpclient.methods.DeleteMethod;  
  14. import org.apache.commons.httpclient.methods.GetMethod;  
  15. import org.apache.commons.httpclient.methods.PostMethod;  
  16. import org.apache.commons.httpclient.methods.PutMethod;  
  17. import org.apache.commons.httpclient.methods.RequestEntity;  
相關文章
相關標籤/搜索