总的来说,hibernate查询方式可以分为以下几种:
HQL,QBC,SQL,QBE,EJBQL
我先来简单解释以下这几种查询方式,具体后面会讲。
HQL:Hibernate Query Language 是面向对象的查询语言, 它和 SQL 查询语言有些相似. 在 Hibernate 提供的各种检索方式中, HQL 是使用最广的一种检索方式。
QBC:Query By Criteria比HQL更加面向对象的一种方式,又称条件查询。
SQL:原生SQL查询,通过SQLQuery接口进行的,通过执行Session.createSQLQuery()获取这个接口
QBE:Query By Example例子查询
EJBQL:与HQL类似,是HQL的一个子集
后两种查询方式用的不多,不再细讲,仅对前三种查询方式进行详细解释
1 HQL查询
HQL的语法和SQL很相似,但是HQL是一种面向对象的查询语句,它的操作对象是类、实例、属性等,而SQL的操作对象 是数据表、列等数据库对象。
由于HQL是完全面向对象的查询语句,因此可以支持继承、多态等特性。
HQL查询依赖于Query类,每一个Query实例对应一个查询对象,它的执行是通过Session的createQuery()方法来获得的。
1.1 HQL查询步骤
s1:获取Hibernate Session对象。
s2:编写HQL语句
s3:以HQL语句作为参数,调用Session的createQuery方法创建查询对象。
s4:如果HQL语句包含参数,则调用Query的setXxx方法为参数赋值。
s5:调用Query对象的list()或uniqueResult()方法返回查询结果列表(持久化实体集)
note:在 HQL 中一些关键字比如 SELECT ,FROM 和 WHERE 等,是不区分大小写的,但是一些属性比如表名和列名是区分大小写的。
举个例子:
private void query(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//以HQL语句创建Query对象,执行setString方法为HQL语句的参数赋值
//Query调用list方法访问查询的全部实例
List list = session.createQuery("select distinct p from Person p where p.name=:name")
.setString("name", "chenssy").list();
//遍历查询结果
for (Iterator iterator = list.iterator();iterator.hasNext();) {
Person p = (Person) iterator.next();
System.out.println("id="+p.getId()+",age="+p.getAge());
}
session.close();
}
上面的程序先编写HQL语句后,使用Session的createQuery(hql)方法创建一个Query,Query对象使用setXxx方法为HQL语句的参数赋值,最后调用list()方法返回查询的全部结果。
在这里Query对象可以连续多次调用setXxx方法为HQL参数赋值。这是因为Hibernate Query的setXxx方法的返回值为Query本身,因此程序创建Query后,可以直接多次调用setXxx方法为HQL语句的参数赋值。
Query对象还包含如下两个方法:
setFirstResult(int firstResult):设置返回的结果集从第几条记录开始
setMaxResult(int maxResult):设置本次查询返回的结果数目
这两个方法用于对HQL查询实现分页控制
1.2 HQL vs SQL
HQL 查询语句是面向对象的, Hibernate 负责解析 HQL 查询语句, 然后根据对象-关系映射文件中的映射信息, 把 HQL 查询语句翻译成相应的 SQL 语句。HQL 查询语句中的主体是域模型中的类及类的属性。
SQL 查询语句是与关系数据库绑定在一起的。SQL 查询语句中的主体是数据库表及表的字段。
1.3 HQL的常用API
实体查询
举个例子:
String hql = "from User";
Query query = session.createQuery(hql);
List<User> list = query.list();
上面的HQL语句将取出User的所有对应记录。
在HQL语句中,本身大小写无关,但是其中出现的类名和属性名必须注意大小写区分。同时,在Hibernate中,查询的目标实体存在继承关系的判定,如果from User将返回所有User以及User子类的记录。假设系统中存在User的两个子类:SysAdmin和SysOperator,那么该hql语句返回的记录将包含这两个子类的所有数据,即使SysAdmin和SysOperator分别对应了不同的库表。
Where子句:
如果我们想取出名为“Erica”的用户记录,可以通过Where子句加以限定(其中AS可以省略):
FROM User AS user WHERE user.name='Erica'
where子句中,我们可以通过比较操作符指定甄选条件,如:
=, <>, <, >, <=, >=, between, not between, in ,not in, is, like等。同时,在where子句中可以使用算术表达式。
几个简单实例:
FROM User user WHERE user.age<20
FROM User user WHERE user.name IS null
FROM User user WHERE user.name LIKE 'Er%'
FROM User user WHERE (user.age % 2 = 1)
FROM User user WHERE (user.age<20) AND (user.name LIKE '%Er')
属性查询
有时,我们需要的数据可能仅仅是实体对象的某个属性(库表记录中的某个字段信息)。通过HQL可以简单的做到这一点。
String hql = "SELECT user.name FROM User user";
List list = session.createQuery(hql).list();
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
上例中,我们指定了只需要获取User的name属性。此时返回的list数据结构中,每个条目都是一个String类型的name数据。
我们也可以通过一条HQL获取多个属性:
public void test() {
String hql = "SELECT user.name,user.age FROM User user";
List list = session.createQuery(hql).list();
Iterator it = list.iterator();
while(it.hasNext()){
Object[] results = (Object[]) it.next();
System.out.println(results[0]+","+results[1]);
}
}
而此时,返回的list数据结构中,每个条目都是一个对象数组(Object[]),其中依次包含了我们所获取的属性数据。
除此之外,我们也可以通过在HQL中动态的构造对象实例的方法对这些平面化的数据进行封装。
String hql = "SELECT new User(user.uName,user.uAge) FROM User user";
List list = session.createQuery(hql).list();
Iterator it = list.iterator();
while(it.hasNext()){
User user = (User) it.next();
System.out.println(user);
}
通过在HQL中动态的构造对象实例,我们实现了对查询结果的对象化封装。此时在查询结果中的User对象仅仅是一个普通的Java对象,仅用于对查询结果的封装,除了在构造时赋予的属性值外,其他属性均为未赋值状态。同时,在实体类中,要提供包含构造属性的构造方法,并且顺序要相同。
与此同时,我们也可以在HQL的select子句中使用统计函数或者利用DSITINCT关键字,剔除重复记录。
SELECT COUNT(*),MIN(user.age) FROM User user
SELECT DISTINCT user.name FROM User user
实体的更新和删除
String hql = "UPDATE User SET age = 18 WHERE id = 1";
int result = session.createQuery(hql).executeUpdate();
上述代码利用HQL语句实现了更新操作。对于单个对象的更新也许代码量并没有减少太多,但如果对于批量更新操作,其便捷性以及性能的提高就相当可观。
例如,以下代码将所有用户的年龄属性更改为10
UPDATE User SET age = 18
HQL的delete子句使用同样很简单,例如以下代码删除了所有年龄大于18的用户记录:
DELETE User WHERE age > 18
不过,需要注意的是,在HQL delete/update子句的时候,必须特别注意它们对缓存策略的影响,极有可能导致缓存同步上的障碍。
分组与排序
Order by子句
举个例子:
FROM User user ORDER BY user.name
默认情况下是按照升序排序,当然我们可以指定排序策略:
FROM User user ORDER BY user.name DESC
order by子句可以指定多个排序条件:
FROM User user ORDER BY user.name, user.age DESC
Group by子句
通过Group by可进行分组统计,如果下例中,我们通过Group by子句实现了同龄用户的统计:
SELECT COUNT(user),user.age FROM User user GROUP BY user.age
通过该语句,我们获得了一系列的统计数据。对于Group by子句获得的结果集而言,我们可以通过Having子句进行筛选。例如,在上例中,我们对同龄用户进行了统计,获得了每个年龄层次中的用户数量,假设我们只对超过10人的年龄组感兴趣,可用以下语句实现:
SELECT COUNT(user),user.age FROM User user GROUP BY user.age HAVING COUNT(user) > 10
参数绑定
SQL注入
在解释参数绑定的使用时,我们先来解释一下什么是SQL注入。
SQL Injection是常见的系统攻击手短,这种攻击方式的目标是针对由SQL字符串拼接造成的漏洞。如,为了实现用户登录功能,我们编写了以下代码:
FROM User user WHERE user.name='"+username+"' AND user.password='"+password+"'
从逻辑上讲,该HQL并没有错误,我们根据用户名和密码从数据库中读取相应的记录,如果找到记录,则认为用户身份合法。
假设这里的变量username和password是来自于网页上输入框的数据。现在我们来做个尝试,在登录网页上输入用户名:”‘Erica’ or ‘x’=’x’”,密码随意,也可以登录成功。
此时的HQL语句为:
FROM User user WHERE user.name='Erica' OR 'x'='x' AND user.password='fasfas'
此时,用户名中的OR ‘x’=’x’被添加到了HQL并作为子句执行,where逻辑为真,而密码是否正确就无关紧要。
这就是SQL Injection攻击的基本原理,而字符串拼接而成的HQL是安全漏洞的源头。参数的动态绑定机制可以妥善处理好以上问题。
Hibernate提供顺序占位符以及引用占位符,将分别举例说明:
顺序占位符:
String hql = "from User user WHERE user.name = ? AND user.age = ?";
List<User> list = session.createQuery(hql).setString(0, "Erica")
.setInteger(1, 10).list();
引用占位符:
String hql = "from User user WHERE user.uName = :name AND user.uAge = :age";
List<User> list = session.createQuery(hql).setString("name", "Erica")
.setInteger("age", 10).list();
我们甚至还可以用一个JavaBean来封装查询参数。
参数绑定机制可以使得查询语法与具体参数数值相互独立。这样,对于参数不同,查询语法相同的查询操作,数据库即可实施性能优化策略。同时,参数绑定机制也杜绝了参数值对查询语法本身的影响,这也就是避免了SQL Injection的可能。
引用查询
我们可能遇到过如下编码规范:“代码中不允许出现SQL语句”。
SQL语句混杂在代码之间将破坏代码的可读性,并似的系统的可维护性降低。为了避免这样的情况,我们通常采取将SQL配置化的方式,也就是说,将SQL保存在配置文件中。Hibernate提供了HQL可配置化的内置支持。
我们可以在实体映射文件中,通过query节点定义查询语句(与class节点同级):
<query name="queryTest"><![CDATA[FROM User user where user.uAge < 20]]></query>
需要注意的是,我们是将HQL语句写入到了xml文件中,所以可能会造成冲突。例如HQL语句的的“<”(小于)与xml的语法有冲突。所以我们会用CDATA将其包裹。
之后我们可以通过session的getNamedQuery方法从配置文件中调用对应的HQL,如:
Query query = session.getNamedQuery("queryTest");
List<User> list = query.list();
for(User user : list){
System.out.println(user);
}
关联查询
关于这部分的内容,参考了很多书上的资料,但都感觉讲的不够清晰,也就是说没有结合到实际的情况中去。下面将按照一个视频教程上的顺序来介绍关联查询。
关于这部分的知识点,是鉴于已经对关联连接查询有所了解的基础上,比如懂得什么是左外连接、内连接等。
下面就开始总结:
HQL迫切左外连接
LEFT JOIN FETCH关键字表示使用迫切左外连接策略。
首先看一下例子中的实体类,这是一个双向1-N的映射(关于1-N的映射在之前的博客中有介绍Hibernate关系映射2:双向1-N关联).
实体类:
public class Department {
private Integer id;
private String name;
private Set<Employee> emps = new HashSet<>();
//省却get和set方法
}
public class Employee {
private Integer id;
private String name;
private float salary;
private String email;
private Department dept;
//省去get和set方法
}
迫切左外连接
String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN FETCH d.emps";
Query query = session.createQuery(hql);
List<Department> depts = query.list();
System.out.println(depts.size());
for (Department dept : depts) {
System.out.println(dept.getName() + "-" + dept.getEmps().size());
}
上面的例子中是想得到所有的部门以及其中的员工。我们通过DISTINCT进行去重。
注意的点:
list()方法中返回的集合中存放实体对象的引用。每个Department对象关联的Employee集合都将被初始化,存放所有关联的Employee的实体对象
查询结果中可能会包含重复元素,可以通过DISTINCT关键字去重,同样由于list中存放的实体对象的引用,所以可以通过HashSet来过滤重复对象。例如:
List<Department> depts = query.list();
depts = new ArrayList<>(new LinkedHashSet(depts));
左外连接
String hql = "FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql);
List<Object[]> result = query.list();
System.out.println(result);
for (Object[] objs : result) {
System.out.println(Arrays.asList(objs));
}
注意的是:通过左外连接返回的list是一个包含Department和与之连接的Employee的object数组。所以我们还需要对数组进行处理,并且有重复。鉴于此,我们可以通过DISTINCT进行处理。
String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql);
List<Department> depts = query.list();
System.out.println(depts.size());
for (Department dept : depts) {
System.out.println(dept.getName() + ", " + dept.getEmps().size());
}
注意:
list方法返回的集合中存放的是对象数组类型
根据配置文件来决定Employee集合的检索策略,比如是fetch、lazy啊或者怎样,还不是像迫切左外连接那样。
我们真正进行的过程中,一般都会使用迫切左外连接。因为迫切左外连接只发送了一条SQL语句将所有信息都查出来,而左外连接就算不使用集合的对象,也会进行查询,而当真正使用集合的时候,又会再去查询。所以性能上迫切左外连接要好。
子查询
子查询可以在HQL中利用另外一条HQL的查询结果。
例如:
FROM User user WHERE (SELECT COUNT(*) FROM user.address) > 1
HQL中,子查询必须出现在where子句中,且必须以一对圆括号包围。
2 QBC查询
2.1 QBC查询说明
条件查询一般是通过以下三个类完成的:
1、Criteria:代表一次查询
2、Criterion:代表一个查询条件
3、Restriction:代表查询条件的工具类
执行条件查询的步骤如下:
1、获得Hibernate的Session对象
2、以Session对象创建Criteria对象
3、使用Restriction的静态方法创建Criterion查询条件
4、向Criteria查询中添加Criterion查询条件
5、执行Criterion的list()方法或者uniqueResult()方法返回结果集
举个例子:
public void query(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//使用ctiteria进行条件查询
List list = session.createCriteria(Person.class)
.add(Restrictions.eq("id", 1))
.list();
for (Iterator iterator = list.iterator();iterator.hasNext();) {
Person person = (Person) iterator.next();
System.out.println(person.getName());
}
}
Criteria对象本身并不具备任何数据过滤筛选功能,但程序可以通过向Criteria对象中组合多个Criterion(每一个Criterion对象代表了一个数据过滤条件)即可实现数据过滤。
Criteria包含如下两个方法:
setFristResult(int firstResult):设置查询返回的第一行记录
setMaxResult(int maxResult):设置查询返回的记录数
这两个方法用于查询分页
Criteria还包含如下几个常用的方法:
add(Criterion criterion):增加查询条件
addOrder(Order order):增加排序规则
List list = session.createCriteria(Person.class)
.add(Restrictions.like("name", "李%")) //增加查询条件
.addOrder(Order.desc("name")) //结果集排序
.setMaxResults(50) //返回的记录数最多为50条 .list(); //返回结果集
Criterion接口代表了一个查询条件,该查询条件有Restrictions负责产生。Restrictions是专门用于产生查询条件的工具类,它的方法大部分是静态方法。
2.2 QBC查询的限制条件
你可以使用 Criteria 对象可用的 add() 方法去添加一个标准查询的限制。
以下是一个示例,它实现了添加一个限制,令返回工资等于 2000 的记录:
Criteria cr = session.createCriteria(Employee.class);
cr.add(Restrictions.eq("salary", 2000));
List results = cr.list();
以下是几个例子,涵盖了不同的情况,可按要求进行使用:
Criteria cr = session.createCriteria(Employee.class);
// To get records having salary more than 2000
cr.add(Restrictions.gt("salary", 2000));
// To get records having salary less than 2000
cr.add(Restrictions.lt("salary", 2000));
// To get records having fistName starting with zara
cr.add(Restrictions.like("firstName", "zara%"));
// Case sensitive form of the above restriction.
cr.add(Restrictions.ilike("firstName", "zara%"));
// To get records having salary in between 1000 and 2000
cr.add(Restrictions.between("salary", 1000, 2000));
// To check if the given property is null
cr.add(Restrictions.isNull("salary"));
// To check if the given property is not null
cr.add(Restrictions.isNotNull("salary"));
// To check if the given property is empty
cr.add(Restrictions.isEmpty("salary"));
// To check if the given property is not empty
cr.add(Restrictions.isNotEmpty("salary"));
你可以模仿以下示例,使用逻辑表达式创建 AND 或 OR 的条件组合:
Criteria cr = session.createCriteria(Employee.class);
Criterion salary = Restrictions.gt("salary", 2000);
Criterion name = Restrictions.ilike("firstNname","zara%");
// To get records matching with OR condistions
LogicalExpression orExp = Restrictions.or(salary, name);
cr.add( orExp );
// To get records matching with AND condistions
LogicalExpression andExp = Restrictions.and(salary, name);
cr.add( andExp );
List results = cr.list();
2.3 分页使用标准
这里有两种分页标准接口方法:
序号 | 方法描述 |
---|---|
1 | public Criteria setFirstResult(int firstResult),这种方法需要一个代表你的结果集的第一行的整数,以第 0 行为开始。 |
2 | public Criteria setMaxResults(int maxResults),这个方法设置了 Hibernate 检索对象的 maxResults。 |
利用上述两种方法结合在一起,我们可以在我们的 Web 或 Swing 应用程序构建一个分页组件。以下是一个例子,利用它你可以一次取出 10 行:
Criteria cr = session.createCriteria(Employee.class);
cr.setFirstResult(1);
cr.setMaxResults(10);
List results = cr.list();
2.4 排序结果
标准 API 提供了 org.hibernate.criterion.order 类可以去根据你的一个对象的属性把你的排序结果集按升序或降序排列。这个例子演示了如何使用 Order 类对结果集进行排序:
Criteria cr = session.createCriteria(Employee.class);
// To get records having salary more than 2000
cr.add(Restrictions.gt("salary", 2000));
// To sort records in descening order
crit.addOrder(Order.desc("salary"));
// To sort records in ascending order
crit.addOrder(Order.asc("salary"));
List results = cr.list();
2.5 预测与聚合
标准 API 提供了 org.hibernate.criterion.projections 类可得到各属性值的平均值,最大值或最小值。Projections 类与 Restrictions 类相似,均提供了几个获取预测实例的静态工厂方法。
以下是几个例子,涵盖了不同的情况,可按要求进行使用:
Criteria cr = session.createCriteria(Employee.class);
// To get total row count.
cr.setProjection(Projections.rowCount());
// To get average of a property.
cr.setProjection(Projections.avg("salary"));
// To get distinct count of a property.
cr.setProjection(Projections.countDistinct("firstName"));
// To get maximum of a property.
cr.setProjection(Projections.max("salary"));
// To get minimum of a property.
cr.setProjection(Projections.min("salary"));
// To get sum of a property.
cr.setProjection(Projections.sum("salary"));
2.6 标准查询示例
考虑下面的 POJO 类:
public class Employee {
private int id;
private String firstName;
private String lastName;
private int salary;
public Employee() {}
public Employee(String fname, String lname, int salary) {
this.firstName = fname;
this.lastName = lname;
this.salary = salary;
}
public int getId() {
return id;
}
public void setId( int id ) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName( String first_name ) {
this.firstName = first_name;
}
public String getLastName() {
return lastName;
}
public void setLastName( String last_name ) {
this.lastName = last_name;
}
public int getSalary() {
return salary;
}
public void setSalary( int salary ) {
this.salary = salary;
}
}
让我们创建以下员工表来存储 Employee 对象:
create table EMPLOYEE (
id INT NOT NULL auto_increment,
first_name VARCHAR(20) default NULL,
last_name VARCHAR(20) default NULL,
salary INT default NULL,
PRIMARY KEY (id)
);
以下是映射文件:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="Employee" table="EMPLOYEE">
<meta attribute="class-description">
This class contains the employee detail.
</meta>
<id name="id" type="int" column="id">
<generator class="native"/>
</id>
<property name="firstName" column="first_name" type="string"/>
<property name="lastName" column="last_name" type="string"/>
<property name="salary" column="salary" type="int"/>
</class>
</hibernate-mapping>
最后,我们将用 main() 方法创建应用程序类来运行应用程序,我们将使用 Criteria 查询:
import java.util.List;
import java.util.Date;
import java.util.Iterator;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.SessionFactory;
import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Projections;
import org.hibernate.cfg.Configuration;
public class ManageEmployee {
private static SessionFactory factory;
public static void main(String[] args) {
try{
factory = new Configuration().configure().buildSessionFactory();
}catch (Throwable ex) {
System.err.println("Failed to create sessionFactory object." + ex);
throw new ExceptionInInitializerError(ex);
}
ManageEmployee ME = new ManageEmployee();
/* Add few employee records in database */
Integer empID1 = ME.addEmployee("Zara", "Ali", 2000);
Integer empID2 = ME.addEmployee("Daisy", "Das", 5000);
Integer empID3 = ME.addEmployee("John", "Paul", 5000);
Integer empID4 = ME.addEmployee("Mohd", "Yasee", 3000);
/* List down all the employees */
ME.listEmployees();
/* Print Total employee's count */
ME.countEmployee();
/* Print Toatl salary */
ME.totalSalary();
}
/* Method to CREATE an employee in the database */
public Integer addEmployee(String fname, String lname, int salary){
Session session = factory.openSession();
Transaction tx = null;
Integer employeeID = null;
try{
tx = session.beginTransaction();
Employee employee = new Employee(fname, lname, salary);
employeeID = (Integer) session.save(employee);
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
return employeeID;
}
/* Method to READ all the employees having salary more than 2000 */
public void listEmployees( ){
Session session = factory.openSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
Criteria cr = session.createCriteria(Employee.class);
// Add restriction.
cr.add(Restrictions.gt("salary", 2000));
List employees = cr.list();
for (Iterator iterator =
employees.iterator(); iterator.hasNext();){
Employee employee = (Employee) iterator.next();
System.out.print("First Name: " + employee.getFirstName());
System.out.print(" Last Name: " + employee.getLastName());
System.out.println(" Salary: " + employee.getSalary());
}
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
}
/* Method to print total number of records */
public void countEmployee(){
Session session = factory.openSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
Criteria cr = session.createCriteria(Employee.class);
// To get total row count.
cr.setProjection(Projections.rowCount());
List rowCount = cr.list();
System.out.println("Total Coint: " + rowCount.get(0) );
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
}
/* Method to print sum of salaries */
public void totalSalary(){
Session session = factory.openSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
Criteria cr = session.createCriteria(Employee.class);
// To get total salary.
cr.setProjection(Projections.sum("salary"));
List totalSalary = cr.list();
System.out.println("Total Salary: " + totalSalary.get(0) );
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
}
}
编译和执行
这是编译并运行上述应用程序的步骤。确保你有适当的 PATH 和 CLASSPATH,然后执行编译程序。
按照在配置一章讲述的方法创建 hibernate.cfg.xml 配置文件。
如上述所示创建 employee.hbm.xml 映射文件。
如上述所示创建 employee.java 源文件并编译。
如上述所示创建 manageemployee.java 源文件并编译。
执行 manageemployee 二进制代码运行程序。
你会得到下面的结果,并且记录将会在 EMPLOYEE 表创建。
$java ManageEmployee
.......VARIOUS LOG MESSAGES WILL DISPLAY HERE........
First Name: Daisy Last Name: Das Salary: 5000
First Name: John Last Name: Paul Salary: 5000
First Name: Mohd Last Name: Yasee Salary: 3000
Total Coint: 4
Total Salary: 15000
如果你检查你的 EMPLOYEE 表,它应该有以下记录:
mysql> select * from EMPLOYEE;
+----+------------+-----------+--------+
| id | first_name | last_name | salary |
+----+------------+-----------+--------+
| 14 | Zara | Ali | 2000 |
| 15 | Daisy | Das | 5000 |
| 16 | John | Paul | 5000 |
| 17 | Mohd | Yasee | 3000 |
+----+------------+-----------+--------+
4 rows in set (0.00 sec)
mysql>
3 原生SQL查询
3.1 SQL查询使用步骤
对原生SQL查询执行的控制是通过SQLQuery接口进行的,通过执行Session.createSQLQuery()获取这个接口。该接口是Query接口的子接口。
执行SQL查询步骤如下:
1、获取Hibernate Session对象
2、编写SQL语句
3、通过Session的createSQLQuery方法创建查询对象
4、调用SQLQuery对象的addScalar()或addEntity()方法将选出的结果与标量值或实体进行关联,分别用于进行标量查询或实体查询
5、如果SQL语句包含参数,调用Query的setXxxx方法为参数赋值
6、调用Query的list方法返回查询的结果集
3.2 标量查询
最基本的 SQL 查询是从一个或多个列表中获取一个标量(值)列表。以下是使用原生 SQL 进行获取标量的值的语法:
String sql = "SELECT first_name, salary FROM EMPLOYEE";
SQLQuery query = session.createSQLQuery(sql);
query.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
List results = query.list();
它将返回一个Object数组组成的List,数组每个元素都是EMPLOYEE表的一个字段值。Hibernate会使用ResultSetMetadata来判定返回的标量值的实际顺序和类型。
但是在JDBC中过多的使用ResultSetMetadata会降低程序的性能。所以为了过多的避免使用ResultSetMetadata或者为了指定更加明确的返回值类型,我们可以使用addScalar()方法:
session.createSQLQuery("SELECT * FROM EMPLOYEE")
.addScalar("name",StandardBasicTypes.STRING)
.addScalar("age",StandardBasicTypes.INT)
.list();
这个查询指定了:
1、SQL查询字符串。
2、要返回的字段和类型。
它仍然会返回Object数组,但是此时不再使用ResultSetMetdata,而是明确的将name和age按照String和int类型从resultset中取出。同时,也指明了就算query是使用*来查询的,可能获得超过列出的这三个字段,也仅仅会返回这三个字段。
如果仅仅只需要选出某个字段的值,而不需要明确指定该字段的数据类型,则可以使用addScalar(String columnAlias)。
从上面可以看出。标量查询中addScalar()方法有两个作用:
1、指定查询结果包含哪些数据列—没有被addScalar选出的列将不会包含在查询结果中。
2、指定查询结果中数据列的数据类型
3.3 实体查询
上面的标量查询返回的标量结果集,也就是从resultset中返回的“裸”数据。如果我们想要的结果是某个对象的实体,这是就可以通过addEntity()方法来实现。addEntity()方法可以讲结果转换为实体。但是在转换的过程中要注意几个问题:
1、查询返回的是某个数据表的全部数据列
2、该数据表有对应的持久化类映射
这时才可以通过addEntity()方法将查询结果转换成实体。
String sql = "SELECT * FROM EMPLOYEE";
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Employee.class);
List results = query.list();
或
session.createSQLQuery("SELECT * FROM EMPLOYEE").addEntity(Employee.class).list;
这个查询指定:
1、SQL查询字符串
2、要返回的实体
3.4 指定 SQL 查询
以下是从原生 SQL 查询中通过 addEntity() 方法和使用指定 SQL 查询来获取实体对象整体的语法:
String sql = "SELECT * FROM EMPLOYEE WHERE id = :employee_id";
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Employee.class);
query.setParameter("employee_id", 10);
List results = query.list();
上面的都是单表查询,如果我们在SQL语句中使用了多表连接,则SQL语句可以选出多个数据表的数据。Hibernate支持将查询结果转换成多个实体。如果要将查询结果转换成多个实体,则SQL字符串中应该为不同数据表指定不同别名,并且调用addEntity()方法将不同数据表转换成不同实体。如下
public void multiEntityQuery(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
String sql = "select p.*,e.* from person_inf as p inner join event_inf as e" +
" on p.person_id=e.person_id";
List list = session.createSQLQuery(sql)
.addEntity("p",Person.class)
.addEntity("e", MyEvent.class)
.list();
for(Iterator iterator = list.iterator();iterator.hasNext();){
//每个集合元素都是Person、MyEvent所组成的数组
Object[] objects = (Object[]) iterator.next();
Person person = (Person) objects[0];
MyEvent event = (MyEvent) objects[1];
System.out.println("person_id="+person.getId()+" person_name="+person.getName()+" title="+event.getTitle());
}
}
3.5 关联和继承
通过提前抓取将Event连接获得,而避免初始化proxy带来的额外开销也是可能的。这是通过addJoin()方法进行的,通过这个方法可以将实体的关联实体转换成查询对象。如下:
public void joinQuery(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
String sql = "select p.*,e.* from person_inf as p,event_inf as e where e.person_id=p.person_id";
List list = session.createSQLQuery(sql)
.addEntity("p",Person.class)
.addJoin("e","p.myEvents")
.list();
for(Iterator iterator = list.iterator();iterator.hasNext();){
//每个集合元素都是Person、MyEvent所组成的数组
Object[] objects = (Object[]) iterator.next();
Person person = (Person) objects[0];
MyEvent event = (MyEvent) objects[1];
System.out.println("person_id="+person.getId()+" person_name="+person.getName()+" title="+event.getTitle());
}
}
上面的程序返回的Person对象,其属性myEvent属性已经完全被初始化,不再需要数据库的额外操作,同时将该属性转换成别名为e的实体。也就是说返回的结果是Person、Event对象数组的列表。
3.6 命名查询
我们可以将SQL语句不放在程序中,而是放在配置文件中。这样可以更好地提高程序解耦。
Hibernate使用
<!-- 命名SQL查询 -->
<sql-query name="sqlquery">
<!-- 将p别名转换为Person实体 -->
<return alias="p" class="Person" />
<!-- 将e别名转换成Event实体 -->
<return alias="e" class="MyEvent" />
<!-- 指定将person_inf表的name属性列作为标量值返回-->
<return-scalar column="p.name" type="string"/>
select p.*,e.* from person_inf as p,event_inf as e where p.person_id = e.person_id and p.age=:age
</sql-query>
使用Session的getNamedQuery即可获得指定命名sql查询。
public void query(){
Session session = HibernateUtil.getSession();
//调用命名查询,直接返回结果
List list = session.getNamedQuery("sqlquery")
.setInteger("age", 30).list();
for(Iterator iterator = list.iterator();iterator.hasNext();){
//每个集合元素都是Person、MyEvent所组成的数组
Object[] objects = (Object[]) iterator.next();
Person person = (Person) objects[0];
MyEvent event = (MyEvent) objects[1];
System.out.println("person_id="+person.getId()+" person_name="+person.getName()+" title="+event.getTitle());
}
session.close();
}
Hibernate允许吧把结果集的映射信息放在
<resultset name="person_resultSet">
<return alias="p" class="Person" />
<return-scalar column="p.age" type="int"/>
</resultset>
通过为
<sql-query name="sqlquery" resultset-ref="person_resultSet">
select p.* from person as p
</sql-query>
3.7 调用存储过程
Hibernate可以通过命名SQL查询来调用存储过程或者函数。对于函数,该函数必须返回一个结果集;对于存储过程,该存储过程的第一个参数必须传出参数,且数据类型是结果集。
下面是一个简单的存储过程:
Create procedure select_person()
Select * from person_inf;
如果需要使用该存储过程,可以先将其定义成命名SQL查询,然后在程序中使用。
当使用原生SQL来调用存储过程,应该为
<sql-query name="callProcedure" callable="true">
<return class="Person">
<!-- 将查询的数据列转换成实体的属性 -->
<return-property name="name" column="name"/>
<return-property name="age" column="age" />
<return-property name="person_id" column="id" />
</return>
</sql-query>
程序与上面相同。
调用存储过程需要注意以下几个问题:
为了在Hibernate中使用存储过程,你必须遵循一些规则.不遵循这些规则的存储过程将不可用.如果你仍然想要使用他们, 你必须通过session.connection()来执行他们.这些规则针对于不同的数据库.因为数据库 提供商有各种不同的存储过程语法和语义.
对存储过程进行的查询无法使用setFirstResult()/setMaxResults()进行分页。
建议采用的调用方式是标准SQL92: { ? = call functionName(<parameters>) }
或者 { ? = call procedureName(<parameters>}
.原生调用语法不被支持。
对于Oracle有如下规则:
函数必须返回一个结果集。存储过程的第一个参数必须是OUT,它返回一个结果集。这是通过Oracle 9或10的SYS_REFCURSOR类型来完成的。在Oracle中你需要定义一个REF CURSOR类型,参见Oracle的手册。
对于Sybase或者MS SQL server有如下规则:
存储过程必须返回一个结果集。.注意这些servers可能返回多个结果集以及更新的数目.Hibernate将取出第一条结果集作为它的返回值, 其他将被丢弃。
如果你能够在存储过程里设定SET NOCOUNT ON,这可能会效率更高,但这不是必需的。
3.8 原生 SQL 的例子
考虑下面的 POJO 类:
public class Employee {
private int id;
private String firstName;
private String lastName;
private int salary;
public Employee() {}
public Employee(String fname, String lname, int salary) {
this.firstName = fname;
this.lastName = lname;
this.salary = salary;
}
public int getId() {
return id;
}
public void setId( int id ) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName( String first_name ) {
this.firstName = first_name;
}
public String getLastName() {
return lastName;
}
public void setLastName( String last_name ) {
this.lastName = last_name;
}
public int getSalary() {
return salary;
}
public void setSalary( int salary ) {
this.salary = salary;
}
}
让我们创建以下 EMPLOYEE 表来存储 Employee 对象:
create table EMPLOYEE (
id INT NOT NULL auto_increment,
first_name VARCHAR(20) default NULL,
last_name VARCHAR(20) default NULL,
salary INT default NULL,
PRIMARY KEY (id)
);
以下是映射文件:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="Employee" table="EMPLOYEE">
<meta attribute="class-description">
This class contains the employee detail.
</meta>
<id name="id" type="int" column="id">
<generator class="native"/>
</id>
<property name="firstName" column="first_name" type="string"/>
<property name="lastName" column="last_name" type="string"/>
<property name="salary" column="salary" type="int"/>
</class>
</hibernate-mapping>
最后,我们将用 main() 方法创建应用程序类来运行应用程序,我们将使用原生 SQL 查询:
import java.util.*;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.SessionFactory;
import org.hibernate.SQLQuery;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.cfg.Configuration;
public class ManageEmployee {
private static SessionFactory factory;
public static void main(String[] args) {
try{
factory = new Configuration().configure().buildSessionFactory();
}catch (Throwable ex) {
System.err.println("Failed to create sessionFactory object." + ex);
throw new ExceptionInInitializerError(ex);
}
ManageEmployee ME = new ManageEmployee();
/* Add few employee records in database */
Integer empID1 = ME.addEmployee("Zara", "Ali", 2000);
Integer empID2 = ME.addEmployee("Daisy", "Das", 5000);
Integer empID3 = ME.addEmployee("John", "Paul", 5000);
Integer empID4 = ME.addEmployee("Mohd", "Yasee", 3000);
/* List down employees and their salary using Scalar Query */
ME.listEmployeesScalar();
/* List down complete employees information using Entity Query */
ME.listEmployeesEntity();
}
/* Method to CREATE an employee in the database */
public Integer addEmployee(String fname, String lname, int salary){
Session session = factory.openSession();
Transaction tx = null;
Integer employeeID = null;
try{
tx = session.beginTransaction();
Employee employee = new Employee(fname, lname, salary);
employeeID = (Integer) session.save(employee);
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
return employeeID;
}
/* Method to READ all the employees using Scalar Query */
public void listEmployeesScalar( ){
Session session = factory.openSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
String sql = "SELECT first_name, salary FROM EMPLOYEE";
SQLQuery query = session.createSQLQuery(sql);
query.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
List data = query.list();
for(Object object : data)
{
Map row = (Map)object;
System.out.print("First Name: " + row.get("first_name"));
System.out.println(", Salary: " + row.get("salary"));
}
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
}
/* Method to READ all the employees using Entity Query */
public void listEmployeesEntity( ){
Session session = factory.openSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
String sql = "SELECT * FROM EMPLOYEE";
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Employee.class);
List employees = query.list();
for (Iterator iterator =
employees.iterator(); iterator.hasNext();){
Employee employee = (Employee) iterator.next();
System.out.print("First Name: " + employee.getFirstName());
System.out.print(" Last Name: " + employee.getLastName());
System.out.println(" Salary: " + employee.getSalary());
}
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
}
}
编译和执行
这是编译并运行上述应用程序的步骤。确保你有适当的 PATH 和 CLASSPATH,然后执行编译程序。
按照在配置一章讲述的方法创建 hibernate.cfg.xml 配置文件。
如上述所示创建 employee.hbm.xml 映射文件。
如上述所示创建 employee.java 源文件并编译。
如上述所示创建 manageemployee.java 源文件并编译。
执行 manageemployee 二进制代码运行程序。
你会得到下面的结果,并且记录将会在 EMPLOYEE 表创建。
$java ManageEmployee
.......VARIOUS LOG MESSAGES WILL DISPLAY HERE........
First Name: Zara, Salary: 2000
First Name: Daisy, Salary: 5000
First Name: John, Salary: 5000
First Name: Mohd, Salary: 3000
First Name: Zara Last Name: Ali Salary: 2000
First Name: Daisy Last Name: Das Salary: 5000
First Name: John Last Name: Paul Salary: 5000
First Name: Mohd Last Name: Yasee Salary: 3000
如果你检查你的 EMPLOYEE 表,它应该有以下记录:
mysql> select * from EMPLOYEE;
+----+------------+-----------+--------+
| id | first_name | last_name | salary |
+----+------------+-----------+--------+
| 26 | Zara | Ali | 2000 |
| 27 | Daisy | Das | 5000 |
| 28 | John | Paul | 5000 |
| 29 | Mohd | Yasee | 3000 |
+----+------------+-----------+--------+
4 rows in set (0.00 sec)
mysql>