JPA SQL 查詢、結果集對映(@NamedNativeQuery、@ColumnResult註解說明)

z1340954953發表於2018-06-29

JPA支援兩種方式:JPQL和條件API,條件API不推薦看。

Java持久化查詢語言JPQL,瞭解下

JPQL 是 實體模型上進行查詢的,而且查詢的結果只能是實體物件或者實體的一個欄位,查詢的是實體物件。

JPQL後面都會去轉化為本地的sql執行。。。

1. 查詢實體的所有資料 /單個欄位

        select e from Employee e 

        select e.name from Employee e 

2. 篩選結果

   JPQL支援where子句,在SQL中通常可用的操作符號在JPQL中也可用,包括,基本比較操作符,in,like ,between,

函式表示式和子查詢等。

3. 實體之間的連線

一個SELECT查詢的結果型別不能是集合,必須是單值物件,如實體的例項或者欄位。支援內連線和外連線

select m from Employee e,Phone p where p.id ='1'

4. 聚合查詢


5. 查詢引數

位置表示法


命名參數列示法


SQL查詢

JPQL是實體上進行查詢的首選方法,使用JPQL更容易在多個資料庫之間移植,並且對映關係更容易維護。sql查詢更方便。

JPQL和SQL查詢定義之間的關鍵區別在於查詢引擎不應解析和解釋特定於供應商的SQL

JPA提供了Query和TypedQuery介面來配置和執行查詢,Query介面用於當結果型別是Object的情況,而TypedQuery介面則是

偏好指定型別的結果的情況。

為了動態地定義一個返回實體結果的SQL查詢,一種是使用EntityManager介面的creatNativeQuery()方法。

String sql = " select * from student_info ";
entityManager.createNativeQuery(sql, Student.class).getResultList();

使用@NamedNativeQuery註解來定義命名的SQL查詢,

這個註解在使用的時候具有唯一性,要求名稱唯一性,在實體上定義多個會報錯,解決方法是使用@NamedNativeQuerys裡面定義多個查詢


改為@NamedNativeQuerys定義多個查詢

@Entity
@NamedNativeQueries(value={
		@NamedNativeQuery(
				name = "studentInfoById",
				query = " select * from student_info where stu_id = ? ",
				resultClass = Student.class
				),
		@NamedNativeQuery(
				name= "studentJoinCourse",
				query = " select * from student_info,course",
				resultSetMapping = "studentAndCourse"
				)
		
})
@Table(name="student_info")
public class Student {

如果結果型別是一個實體,那麼resutlClass元素可用指示實體類,如果結果需要一個SQL對映,那麼resutlSetMapping元素可用

用來指定對映的名稱。

執行命名SQL查詢

下面用到的表,學生和課程的關係,按照一對多的關係測試。

student_info 


course_info


執行命名SQL查詢可以使用EntityManager介面的createNamedQuery()方法建立和執行這個查詢,並且這個方法能夠返回一個指定了型別的query,TypedQuery。而不是createNativeQuery()返回一個非型別的Query(Object的實體型別)

public class StudentDao {
	public Student queryStudentbyId(EntityManager entitymanager,Integer id){
		return entitymanager.createNamedQuery("studentInfoById", Student.class).setParameter(1, 1).getSingleResult();
	}

關於返回的實體的SQL查詢,需要注意的是,由於實體的例項在持久化上下文中是託管狀態,實體的內容/和修改後的實體的內容都將在事務提交後寫入到資料庫中。

通常的情況是,如果查詢中忽略對映一個欄位/或者預設為某個值,此時假設每個對映欄位,這個值為null,然後修改結果提交了事務,資料庫的預設值就會改變,變成了null,這就存在問題,這點需要注意。

JPA支援SQL資料操作語句(INSERT,UPDATE,DELETE)

public class StudentDao {
	public static final String deleteStudentId = "delete from student_info where stu_id = ? ";
	public static final String updateStudentId = " update student_inf set stu_name = ? where stu_id = ? ";
	public void deleteStudentById(EntityManager entitymanager,Integer id ){
		 entitymanager.createNativeQuery(deleteStudentId).setParameter(1, id).executeUpdate();
	}
	public void updateStudentById(EntityManager entitymanager,Integer id ){
		entitymanager.createNativeQuery(updateStudentId).setParameter(1, id).executeUpdate();
	}

通常不鼓勵執行SQL語句來更改實體所對映的表中資料,這會導致快取的實體和資料庫表不一致,遇到這種情況,不要使用查詢出來的快取物件,最好再次發起一次查詢方法。

SQL結果集對映

使用註解@SqlResultSetMapping註解定義SQL結果集對映,可以放在一個實體類上,有一個名稱(在永續性單元中唯一)和一個或者多個實體對映組成。

直接在實體上使用多個@SqlResultSetMapping會報錯,解決方法是使用@SqlResultSetMappings註解,在這個註解中定義多個@SqlResultSetMapping

@SqlResultSetMappings({
	@SqlResultSetMapping(name="studentInfo",entities=@EntityResult(entityClass=Student.class)),
	})

元素name表示名稱必須唯一,entites表示對映的實體物件,可以是多個

使用實體類進行對映的時候,必須查詢出所有實體類的欄位,包括外來鍵。如果缺少欄位,報錯或者部分構建實體,取決於JPA的供應商。

這個定義一個單一的結果對映,由@EntityResult註解指定,其引用了Employee實體類,該查詢必須為實體對映的列提供值,包括外來鍵。

如何使用結果集對映呢,resultSetMapping屬性為使用的結果集名稱。


*  對映外來鍵

假設學生和課程的關係是一對多,課程表中外來鍵列存放學生表的主鍵,定義一個本地查詢

@Entity
@NamedNativeQuery(name="queryCourseInfoByid",
			query = " select course_id,course_name,stu_id from course_info where course_id = ? ",
			resultSetMapping = "courseInfo"
			)
@SqlResultSetMapping(name="courseInfo",entities=@EntityResult(entityClass=Course.class))
@Table(name="course_info")
public class Course {
public class CourseDao {
	public Course queryCouserInfoById(EntityManager entityManager,Integer id ){
		return entityManager.createNamedQuery("queryCourseInfoByid", Course.class).setParameter(1, id).getSingleResult();
	}
}

那麼在執行本地查詢的時候,查詢的時候會根據外來鍵列,查詢關聯實體,這樣會傳送兩次查詢

最好設定實體中關聯物件的欄位為懶載入。需要注意快取的是外來鍵列的值。

CourseDao dao = new CourseDao();
Course course = dao.queryCouserInfoById(entityManager, 1);
列印SQL:
Hibernate: 
    select
        course_id,
        course_name,
        stu_id 
    from
        course_info 
    where
        course_id = ?
Hibernate: 
    select
        student0_.stu_id as stu_id1_19_0_,
        student0_.stu_sex as stu_sex2_19_0_,
        student0_.stu_age as stu_age3_19_0_,
        student0_.stu_name as stu_name4_19_0_ 
    from
        student_info student0_ 
    where
        student0_.stu_id=?

*  對映列別名

在查詢的結果不同於列對映名稱的情況下,@FieldResult註解將列的別名對映到實體的欄位上。

	@SqlResultSetMapping(name="studentInfo",
			entities={
			@EntityResult(entityClass=Student.class,fields={
				@FieldResult(name="stuId",column="student_id"),
				@FieldResult(name="sex",column="stu_sex"),
				@FieldResult(name="stuName",column="stu_name"),
				@FieldResult(name="stuAge",column="stu_age")
			}
@NamedNativeQuery(
			name = "studentInfoById",
			query = " select stu_id as student_id, stu_sex ,stu_name,stu_age from student_info where stu_id = ? ",
			resultSetMapping = "studentInfo"
			)

測試中發現,實體對映的一個列使用別名,其他的列也需要手動使用@FieldResult註解對映,奇怪。

* 查詢返回的欄位不是實體欄位(標量結果列)對映方式

 對映的欄位不是實體結果型別的欄位,稱為標量結果型別,藉助@ColumnResult註解定義對映欄位,很多時候不需要實體的

全部欄位,只需要部分欄位,可以使用@ColumnResult進行欄位對映。

並且註解@ColumnResult的name元素的值為列名

@NamedNativeQuery(name="stuCousrseInfos",
				query = "select m.stu_id,m.stu_name,n.course_name from student_info m join course_info n on m.stu_id = n.stu_id"
				+"and m.stu_id = ? ",
				resultSetMapping = "stuCousrseInfosMapping"
				)

結果集對映定義

@SqlResultSetMapping(name="stuCousrseInfosMapping",
			columns={
				@ColumnResult(name="stu_id"),
				@ColumnResult(name="stu_name"),
				@ColumnResult(name="course_name")
	}

Dao如何執行查詢,取出對映欄位,無法確定型別,使用Object[]陣列接收

public List<Object[]> queryjoinInfo(EntityManager entityManager,Integer stuId){
		List<Object[]> list = entityManager.createNamedQuery("stuCousrseInfos").setParameter(1, 1).getResultList();
		return list;
	}

Object[]陣列中對映欄位的值的順序和在結果集中定義的順序一致,通過上面的方式可以用於多表join的欄位對映上,不過

得到的結果是Object[]陣列形式,需要去遍歷獲取。

*  返回的欄位是多個實體的欄位的對映反射(多個結果對映)

查詢可能返回的的欄位是屬於多個實體的欄位,當查詢的實體之間存在一對一關係時,比較合適,如果不是一對一方法,

,比如是一對多的情況,就是導致一的一方,重複的例項生成。

@NamedNativeQuery(
				name= "studentJoinCourse",
				query = " select * from student_info m ,course_info n where m.stu_id = ? and n.course_id = ? ",
				resultSetMapping = "studentAndCourse"
				)
@SqlResultSetMapping(name="studentAndCourse",
	entities ={@EntityResult(entityClass=Student.class),@EntityResult(entityClass=Course.class)})
@SuppressWarnings("unchecked")
	public List<Object[]> querystudentJoinCourse(EntityManager entitymanager,Integer stuId,Integer courseId){
		List<Object[]> list =  entitymanager.createNamedQuery("studentJoinCourse").setParameter(1, stuId).setParameter(2, courseId).getResultList();
		 return list;
	}

從結果看出,欄位對映的順序按照實體類的順序進行對映。

引數繫結

定義本地查詢的時候使用?作為佔位符號,setParameter方法按照下標設值,下標從1開始,如果是日期型別需要特殊處理

setParameter(1, new Date(),TemporalType.DATE)
public enum TemporalType
{
  DATE,  TIME,  TIMESTAMP;
  
  private TemporalType() {}
}
根據需要設定為DATE,TIME,TIMESTAMP






相關文章