1. 程式人生 > 程式設計 >spring data jpa 查詢自定義欄位,轉換為自定義實體方式

spring data jpa 查詢自定義欄位,轉換為自定義實體方式

目標:查詢資料庫中的欄位,然後轉換成 jsON 格式的資料,返回前臺。

環境:idea 2016.3.4, jdk 1.8, mysql 5.6, spring-boot 1.5.2

背景:首先建立 entity 對映資料庫(非專業 java 不知道這怎麼說)

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String userName;    // 賬號
    private String password;    // 密碼
    // getter setter 方法略過
}

然後建立與之對應的 model

public class UserModel implements Serializable {
    // 一些屬性
}

這裡我們分情況討論

首先第一種情況:

查詢的欄位與表中的欄位全部對應(就是查表裡所有的欄位,但是使用 Model 作為接收物件)

這種情況比較簡單,呼叫 Repository 提供的方法,返回一個 entity,然後將 entity 的屬性複製到 model 中。像這樣

UserModel user = new UserModel();
User userEntity = new User();
// 一個工具類,具體使用方法請百度
BeanUtils.copyProperties(user,userEntity);

第二種情況:只查詢指定的幾個欄位

現在我有張表,有欄位如下:

@Entity
@Table(name = "user_info")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name = "使用者";     // 暱稱
    private String signature;         // 個性簽名
    private String gender = "未知";   // 性別
    private String description;       // 個人說明
    private String avatar;            // 頭像
    private Long role;                // 許可權
    private Boolean disable;          // 是否凍結
    private Date createTime;          // 建立時間
    private Boolean isDelete;         // 是否刪除
    private Long userId;              // 使用者 Id
    // ...
}

但是我只需要查詢指定的幾個欄位,然後轉換成 JSON,返回給前臺,咋辦呢?

第一種方法:使用 model 查詢時轉化

首先建立一個 model ,寫上自己想要查詢的欄位,然後寫上建構函式,這步很重要,因為spring jpa 轉化時會呼叫這個構造方法

public class MyModel implements Serializable {
    private String userName;
    private String name;
    private String gender;
    private String description;
    public MyModel() {};
    public MyModel(String userName,String name,String gender,String description) {
        this.userName = userName;
        this.name = name;
        this.gender = gender;
        this.description = description;
    }
}

然後在 dao 類中寫查詢方法

@Query(value = "select new pers.zhuch.model.MyModel(u.userName,ui.name,ui.gender,ui.description) from UserInfo ui,User u where u.id = ui.userId")
public List<MyModel> getAllRecord();

直接在查詢語句中 new model 框架底層會呼叫它,然後返回這個物件(這裡我寫了完整的類路徑,不寫的時候它報錯說找不到型別什麼的)

然後就可以獲得只有指定欄位的 model 了。然後就把它轉成 JSON 格式就 O 了。

第二種方法:在service 裡邊轉換成 JSON

原理其實和第一種方法差不多,只是處理結果的方式不太一樣,只是這種方法我們就不在 hql 中 new Model 了,直接寫查詢方法

@Query(value = "select new map(u.userName,User u where u.id = ui.userId")
public List<Map<String,Object>> getCustomField();

直接new map(這裡得是小寫,不知道大寫有木有問題,反正沒試,編譯器提示是要小寫的)

然後返回的結果是這樣的

[
{
"0": "admin",
"1": "你猜",
"2": "男",
"3": "一段描述"
},{
"0": "abc",
"1": "你猜人家",
&nbshttp://www.cppcns.comp; "2": "女",
"3": "沒事先掛了"
}
]

然後在 service 層裡直接封裝成 JSON 物件,返回

List<JsonObject> list = new ArrayList();
for(Map map : result) {
    JsonObject j = new JsonObject();
    j.addProperty(attrName,val);
    ...
    list.add(j);
}
gson.toJson(list);

還有一種返回結果,這樣寫:

@Query(value = "select u.userName,ui.description from UserInfo ui,User u where u.id = ui.userId")
public List<Object> getCustomField();

返回結果是這樣的格式:

[
[
"admin",
"你猜",
"男",
"一段描述"
],[
"abc",
"你猜人家",
"女",
"沒事先掛了"
]
]

返回的是陣列,也一樣可以通過上面的方法轉成 json ,這裡我的程式中出現了一點點 BUG,就是空值的欄位不會在陣列中,不知道為什麼。

這種方法必須明確的知道查詢了哪些欄位,靈活性比較差,雖然它解決了手頭的問題。還有就是版本的不同,有可能會出現丟失空欄位的情況,我個人特別的不喜歡這樣的方法,萬一我實體幾十個欄位,寫著寫著忘了寫到哪了,就 over 了

第三種方法:返回一個便於轉換成 json 格式的 list

其實和上面很相似,都是 dao 層返回一個 List < Map < String,Object >>,但是上面的結果集返回的 Map 的 key 只是列的下標,這種方式稍微理想一點點,就是 Map 的 key 就是查詢的列名。

但是這種方式需要實現自定義 Repository( 這裡不詳細介紹,請自行百度 ),並且只是 jpa 整合 hibenate 的時候可以使用。

public List getCustomEntity() {
    String sql = "select t.id,t.name,t.gender,t.is_delete,t.create_time,t.description from t_entity t";
    Query query = em.createNativeQuery(sql);
    // Query 介面是 spring-data-jpa 的介面,而 SQLQuery 介面是 hibenate 的介面,這裡的做法就是先轉成 hibenate 的查詢介面物件,然後設定結果轉換器
    query.un程式設計客棧wrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
    return query.getResultList();
}

這種方法返回的就是比較標準的 JSON 格式的 java 物件了,只需要用 jackson 或者 Gson 轉一下就www.cppcns.com是標準的 json 了

[
{
attr: val,
...
},
{
attr: val,
]

這種方式其實已經比較理想了,因為直接就能返回到前臺,但是有時候,結果不是一條 sql 能夠解決的,得兩條或者以上的 sql 來解決一個複雜的查詢需求程式設計客棧,這個過程中,結果比較需要轉換成 pojo,以便於組裝操作。

第四種方案:dao 中直接轉成 pojo 返回

這個方案還是依賴於 hibenate,有點操蛋,但是更明確一些。

public List getCustomEntity() {
    String sql = "select t.id,t.is_delete as isEnable,t.create_time as createTime,t.description from t_entity t";
    Query query = em.createNativeQuery(sql);
    query.unwrap(SQLQuery.class)
    // 這裡是設定欄位的資料型別,有幾點注意,首先這裡的欄位名要和目標實體的欄位名相同,然後 sql 語句中的名稱(別名)得與實體的相同
            .addScalar("id",StandardBasicTypes.LONG)
            .addScalar("name",StandardBasicTypes.STRING)
            .addScalar("gender",StandardBasicTypes.STRING)
            .addScalar("isEnable",StandardBasicTypes.BOOLEAN)
            .addScalar("createTime",StandardBasicTypes.STRING)
            .addScalar("descrKpGBxCqokmiption",StandardBasicTypes.STRING)
            .setResultTransformer(Transformers.aliasToBean(EntityModel.class));
    return query.getResultList();
}

這次返回的就是 List 了。這裡要注意的是 StandardBasicTypes這個常量類,在一些舊版本中,是 Hibenate 類,具體哪個包我不知道,我這個版本中是換成了前面的那個常量類

繼承jpa Repository 寫自定義方法查詢

今天在寫jpa查詢的時候,遇到了新增自定義方法,專案啟動報錯原因,現總結如下:

首先定義實體類

@Entity
@Table(name = "user")
Class User{
     @Id
    @GeneratedValue 
      int id;
      @Column
      String age;
      @Column
      String school;
      @Column
      String userName;
  set,get方法 (省略)
}
public interface UserRepository extends JpaRepository<User,Long> {
      List<User> findByUsernameLike(String username);
     List<User> aaa();
}

啟動專案時,專案報錯提示資訊為:

org.springframework.data.mapping.PropertyReferenceException: No property aaa found for type com.fpi.safety.common.entity.po.User

再將List<User> aaa();方法去掉後,專案又可以正常啟動執行

是什麼原因呢?

經查詢,原來是繼承jpa,必須滿足一些規則,規則如下

spring data jpa 查詢自定義欄位,轉換為自定義實體方式

spring data jpa 查詢自定義欄位,轉換為自定義實體方式

Spring Data JPA框架在進行方法名解析時,會先把方法名多餘的字首擷取掉,比如find,findBy,read,readBy,get,getBy,然後對剩下的部分進行解析。

假如建立如下的查詢:findByUserName(),框架在解析該方法時,首先剔除findBy,然後對剩下的屬性進行解析,假設查詢實體為User

1:先判斷userName(根據POJO規範,首字母變為小寫)是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;

2:從右往左擷取第一個大寫字母開頭的字串此處是Name),然後檢查剩下的字串是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左擷取;最後假設使用者為查詢實體的一個屬性;

3:接著處理剩下部分(UserName),先判斷使用者所對應的型別是否有userName屬性,如果有,則表示該方法最終是根據“User.userName”的取值進行查詢;否則繼續按照步驟2的規則從右往左擷取,最終表示根據“User.userName”的值進行查詢。

4:可能會存在一種特殊情況,比如User包含一個的屬性,也有一個userNameChange屬性,此時會存在混合。可以明確在屬性之間加上“_”以顯式表達意思,比如“findByUser_NameChange )“或者”findByUserName_Change()“

從上面,我們可以得知,jap在解析是,aaa在user類中是沒有屬性的,所以報錯No property aaa found.

如果我們想要使用jap框架,又不想再多增加一個自定義類,則必須符合其命名規則

如果,你記不住jpa的規則也沒關係,你可以自己再多寫一個類來實現自定義查詢方法

如下:

1. 自定義一個介面,該介面用來宣告自己額外定義的查詢。

public interface UseerRepositoryTwo {
    public List<User> searchUser(String name,int id);
}

2. 建立一個介面,該介面 extends JpaRepository 或者 CurdRepository, 以及上面自己定義的介面 UseerRepositoryTwo

public interface UserRepositoryTwoService extends CrudRepository<LogDTO,Integer>,CustomizedLogRepository {
}

3. 實現UserRepositoryTwoService

注意此處的類名,必須以 2 中建立的介面的名字UserRepositoryTwoService,後面加上 Impl 來宣告,而不是寫成 UseerRepositoryTwoImpl

public class UserRepositoryTwoServiceImpl implements UserRepositoryTwoService {
    @Autowired
    @PersistenceContext
    private EntityManager entityManager;
    @Override
    public List<User> searchLogs(int Id,String name) {
        ......
    }
}

自己在寫自定義實現即可

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。