Hibernate @NamedQuery 命名查询:实用示例与最佳实践

欢迎来到Hibernate命名查询示例教程。在之前的教程中,我们学习了如何在Hibernate中使用HQL和原生SQL查询。然而,当项目中存在大量查询时,这些查询分散在各处会导致代码难以管理和维护。为了解决这个问题,Hibernate提供了命名查询(Named Query)功能。通过命名查询,我们可以在一个中心位置定义所有查询,并在代码的任何地方重复使用它们。Hibernate命名查询既可以用于HQL,也可以用于原生SQL。

Hibernate命名查询的定义方式

Hibernate命名查询可以通过两种主要方式定义:在Hibernate映射文件(如`.hbm.xml`)中,或者通过JPA注解`@NamedQuery`和`@NamedNativeQuery`。本文将深入探讨这两种定义方式,并通过一个简单的应用程序演示如何使用Hibernate命名查询。我们将沿用HQL示例中使用的数据库表结构,因此您可以参考之前的文章获取数据库设置的SQL脚本。在本次Hibernate命名查询示例项目中,我们将主要采用注解方式进行映射,但也会在映射文件和实体Bean类中创建一些命名查询。最终的项目结构如下图所示,我们将重点关注与Hibernate命名查询相关的组件。

Hibernate配置XML文件:`hibernate.cfg.xml`

<?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE hibernate-configuration SYSTEM "https://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.password">scdev123</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
		<property name="hibernate.connection.username">scdev</property>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

		<property name="hibernate.current_session_context_class">thread</property>
		<property name="hibernate.show_sql">true</property>

		<mapping class="com.Olivia.hibernate.model.Employee" />
		<mapping class="com.Olivia.hibernate.model.Address" />
		<mapping resource="named-queries.hbm.xml" />
	</session-factory>
</hibernate-configuration>

Hibernate命名查询XML文件:`named-queries.hbm.xml`

我们有一个专门的Hibernate映射文件,其中只包含HQL命名查询和原生SQL命名查询。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"https://hibernate.org/dtd/hibernate-mapping-3.0.dtd">
	
<hibernate-mapping>
	<query name="HQL_GET_ALL_EMPLOYEE">from Employee</query>

	<query name="HQL_GET_EMPLOYEE_BY_ID">
		<![CDATA[from Employee where emp_id = :id]]>
	</query>

	<query name="HQL_GET_EMPLOYEE_BY_SALARY">
		<![CDATA[from Employee where emp_salary > :salary]]>
	</query>
	
	<sql-query name="SQL_GET_ALL_EMPLOYEE">
		<![CDATA[select emp_id, emp_name, emp_salary from Employee]]>
	</sql-query>
	
	<sql-query name="SQL_GET_ALL_EMP_ADDRESS">
		<![CDATA[select {e.*}, {a.*} from Employee e join Address a ON e.emp_id=a.emp_id]]>
		<return alias="e" class="com.Olivia.hibernate.model.Employee" />
		<return-join alias="a" property="e.address"></return-join>
	</sql-query>
</hibernate-mapping>

在上述配置中,`<query>`元素用于定义HQL命名查询,而`<sql-query>`元素用于定义原生SQL命名查询。我们可以使用`<return>`元素来声明将映射到结果集的实体。当涉及到多表连接查询时,我们应该使用`<return-join>`元素来处理关联关系。为了确保查询字符串中的特殊字符(如`<`和`>`)不会破坏XML文件的结构,我们应该使用`CDATA`块来包裹Hibernate命名查询的实际内容。

通过Hibernate注解`@NamedQuery`定义命名查询

这是文章《Hibernate命名查询示例 – @NamedQuery》的第2部分(共4部分)。

我们定义了两个模型类:雇员(Employee)地址(Address)。以下是在地址(Address)类中定义的查询:

package com.Olivia.hibernate.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;

@Entity
@Table(name = "ADDRESS")
@NamedQueries({ @NamedQuery(name = "@HQL_GET_ALL_ADDRESS", 
			query = "from Address") })
@NamedNativeQueries({ @NamedNativeQuery(name = "@SQL_GET_ALL_ADDRESS", 
			query = "select emp_id, address_line1, city, zipcode from Address") })
public class Address {

	@Id
	@Column(name = "emp_id", unique = true, nullable = false)
	@GeneratedValue(generator = "gen")
	@GenericGenerator(name = "gen", strategy = "foreign", parameters = { @Parameter(name = "property", value = "employee") })
	private long id;

	@Column(name = "address_line1")
	private String addressLine1;

	@Column(name = "zipcode")
	private String zipcode;

	@Column(name = "city")
	private String city;

	@OneToOne
	@PrimaryKeyJoinColumn
	private Employee employee;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getAddressLine1() {
		return addressLine1;
	}

	public void setAddressLine1(String addressLine1) {
		this.addressLine1 = addressLine1;
	}

	public String getZipcode() {
		return zipcode;
	}

	public void setZipcode(String zipcode) {
		this.zipcode = zipcode;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public Employee getEmployee() {
		return employee;
	}

	public void setEmployee(Employee employee) {
		this.employee = employee;
	}

	@Override
	public String toString() {
		return "AddressLine1= " + addressLine1 + ", City=" + city
				+ ", Zipcode=" + zipcode;
	}
}

测试程序的Hibernate命名查询

我们来编写一个测试程序,使用上述定义的所有 Hibernate 命名查询。

package com.Olivia.hibernate.main;

import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.Olivia.hibernate.model.Address;
import com.Olivia.hibernate.model.Employee;
import com.Olivia.hibernate.util.HibernateUtil;

public class HibernateNamedQueryExample {

	@SuppressWarnings("unchecked")
	public static void main(String[] args) {

		// 准备工作
		SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
		Session session = sessionFactory.getCurrentSession();
		Transaction tx = session.beginTransaction();

		// HQL 命名查询示例
		Query query = session.getNamedQuery("HQL_GET_ALL_EMPLOYEE");
		List<Employee> empList = query.list();
		for (Employee emp : empList) {
			System.out.println("员工列表::" + emp.getId() + "," + emp.getAddress().getCity());
		}

		query = session.getNamedQuery("HQL_GET_EMPLOYEE_BY_ID");
		query.setInteger("id", 2);
		Employee emp = (Employee) query.uniqueResult();
		System.out.println("员工姓名=" + emp.getName() + ", 城市=" + emp.getAddress().getCity());

		query = session.getNamedQuery("HQL_GET_EMPLOYEE_BY_SALARY");
		query.setInteger("salary", 200);
		empList = query.list();
		for (Employee emp1 : empList) {
			System.out.println("员工列表::" + emp1.getId() + "," + emp1.getSalary());
		}

		query = session.getNamedQuery("@HQL_GET_ALL_ADDRESS");
		List<Address> addressList = query.list();
		for (Address addr : addressList) {
			System.out.println("地址列表::" + addr.getId() + "::" + addr.getZipcode() + "::" + addr.getEmployee().getName());
		}
		
		// 原生 SQL 命名查询示例
		query = session.getNamedQuery("@SQL_GET_ALL_ADDRESS");
		List<Object[]> addressObjArray = query.list();
		for(Object[] row : addressObjArray){
			for(Object obj : row){
				System.out.print(obj + "::");
			}
			System.out.println("\n");
		}
		
		query = session.getNamedQuery("SQL_GET_ALL_EMP_ADDRESS");
		addressObjArray = query.list();
		for(Object[] row : addressObjArray){
			Employee e = (Employee) row[0];
			System.out.println("员工信息::"+e);
			Address a = (Address) row[1];
			System.out.println("地址信息::"+a);
		}
		// 回滚以保存测试数据
		tx.commit();

		// 关闭 Hibernate 资源
		sessionFactory.close();
	}

}

当我们用我们拥有的测试数据执行以上程序时,它会生成以下输出。

Hibernate: select employee0_.emp_id as emp_id1_1_, employee0_.emp_name as emp_name2_1_, employee0_.emp_salary as emp_sala3_1_ from EMPLOYEE employee0_
Hibernate: select address0_.emp_id as emp_id1_0_0_, address0_.address_line1 as address_2_0_0_, address0_.city as city3_0_0_, address0_.zipcode as zipcode4_0_0_, employee1_.emp_id as emp_id1_1_1_, employee1_.emp_name as emp_name2_1_1_, employee1_.emp_salary as emp_sala3_1_1_ from ADDRESS address0_ left outer join EMPLOYEE employee1_ on address0_.emp_id=employee1_.emp_id where address0_.emp_id=?
Hibernate: select address0_.emp_id as emp_id1_0_0_, address0_.address_line1 as address_2_0_0_, address0_.city as city3_0_0_, address0_.zipcode as zipcode4_0_0_, employee1_.emp_id as emp_id1_1_1_, employee1_.emp_name as emp_name2_1_1_, employee1_.emp_salary as emp_sala3_1_1_ from ADDRESS address0_ left outer join EMPLOYEE employee1_ on address0_.emp_id=employee1_.emp_id where address0_.emp_id=?
Hibernate: select address0_.emp_id as emp_id1_0_0_, address0_.address_line1 as address_2_0_0_, address0_.city as city3_0_0_, address0_.zipcode as zipcode4_0_0_, employee1_.emp_id as emp_id1_1_1_, employee1_.emp_name as emp_name2_1_1_, employee1_.emp_salary as emp_sala3_1_1_ from ADDRESS address0_ left outer join EMPLOYEE employee1_ on address0_.emp_id=employee1_.emp_id where address0_.emp_id=?
Hibernate: select address0_.emp_id as emp_id1_0_0_, address0_.address_line1 as address_2_0_0_, address0_.city as city3_0_0_, address0_.zipcode as zipcode4_0_0_, employee1_.emp_id as emp_id1_1_1_, employee1_.emp_name as emp_name2_1_1_, employee1_.emp_salary as emp_sala3_1_1_ from ADDRESS address0_ left outer join EMPLOYEE employee1_ on address0_.emp_id=employee1_.emp_id where address0_.emp_id=?
员工列表::1,San Jose
员工列表::2,Santa Clara
员工列表::3,Bangalore
员工列表::4,New Delhi
Hibernate: select employee0_.emp_id as emp_id1_1_, employee0_.emp_name as emp_name2_1_, employee0_.emp_salary as emp_sala3_1_ from EMPLOYEE employee0_ where emp_id=?
员工姓名=David, 城市=Santa Clara
Hibernate: select employee0_.emp_id as emp_id1_1_, employee0_.emp_name as emp_name2_1_, employee0_.emp_salary as emp_sala3_1_ from EMPLOYEE employee0_ where emp_salary>?
员工列表::3,300.0
员工列表::4,400.0
Hibernate: select address0_.emp_id as emp_id1_0_, address0_.address_line1 as address_2_0_, address0_.city as city3_0_, address0_.zipcode as zipcode4_0_ from ADDRESS address0_
地址列表::1::95129::Pankaj
地址列表::2::95051::David
地址列表::3::560100::Lisa
地址列表::4::100100::Jack
Hibernate: select emp_id, address_line1, city, zipcode from Address
1::Albany Dr::San Jose::95129::

2::Arques Ave::Santa Clara::95051::

3::BTM 1st Stage::Bangalore::560100::

4::City Centre::New Delhi::100100::

Hibernate: select e.emp_id as emp_id1_1_0_, e.emp_name as emp_name2_1_0_, e.emp_salary as emp_sala3_1_0_, a.emp_id as emp_id1_0_1_, a.address_line1 as address_2_0_1_, a.city as city3_0_1_, a.zipcode as zipcode4_0_1_ from Employee e join Address a ON e.emp_id=a.emp_id
员工信息::Id= 1, Name= Pankaj, Salary= 100.0, {Address= AddressLine1= Albany Dr, City=San Jose, Zipcode=95129}
地址信息::AddressLine1= Albany Dr, City=San Jose, Zipcode=95129
员工信息::Id= 2, Name= David, Salary= 200.0, {Address= AddressLine1= Arques Ave, City=Santa Clara, Zipcode=95051}
地址信息::AddressLine1= Arques Ave, City=Santa Clara, Zipcode=95051
员工信息::Id= 3, Name= Lisa, Salary= 300.0, {Address= AddressLine1= BTM 1st Stage, City=Bangalore, Zipcode=560100}
地址信息::AddressLine1= BTM 1st Stage, City=Bangalore, Zipcode=560100
员工信息::Id= 4, Name= Jack, Salary= 400.0, {Address= AddressLine1= City Centre, City=New Delhi, Zipcode=100100}
地址信息::AddressLine1= City Centre, City=New Delhi, Zipcode=100100

关于 Hibernate 命名查询的重要提示

Hibernate命名查询的几个重要要点如下:

  1. Hibernate命名查询将查询集中管理,避免了查询语句分散在代码各处。
  2. 在创建Hibernate会话工厂时,Hibernate命名查询的语法会被校验。如果命名查询存在任何错误,应用程序会快速报错,便于及时发现问题。
  3. Hibernate命名查询是全局性的,这意味着一旦定义,就可以在整个应用程序中重复使用。

命名查询的一个主要缺点是难以调试,因为我们需要找到其定义的位置。

这就是Hibernate命名查询示例的全部内容。你可以通过以下链接下载示例项目。

下载Hibernate命名查询项目

bannerAds