文章目录
Criteria
条件查询是更具面向对象特色的数据查询方式。它是一种类型安全的查询方式,用来替代HQL。它是如何保证类型安全的呢?它使用的是强类型这种方式去构造criteria 查询的,利用的就是静态元模型。
静态元模型
这个东西,是个什么东东,我在简单说一下,当我们在查询的时候,我们面向对象查询的,所以要写属性,你必须要记住属性名,这就很麻烦了,但是通过静态元模型,会通过工具自动帮你去生成静态元模型类,直接通过类去找属性,就很爽。
配置静态元模型生成
这里是基于Myeclipse,其他配置自行百度。
在这里首先需要注意Hibernate 5.3 静态元模型,jdk 要在1.8 否则会编译不通过。
右击项目-属性
注意这里产生目录,可以使用默认,也可以是自己设置。
经过上面两步的设置,如果项目能正确的编译,那么恭喜你,没问题了。
自动生成静态元数据
我们这里是基于Hibernate注解,还可以是基于persistence.xml。
@Entity//这里注解不可以少,不然,无法为该实体类生成静态元数据
public class Phone {
private Integer number;
private Integer number_id;
private String phone_name;
}
产生的静态元数据类如下:
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Phone.class)
public abstract class Phone_ {
public static volatile SingularAttribute<Phone, Integer> number;
public static volatile SingularAttribute<Phone, Integer> number_id;
public static volatile SingularAttribute<Phone, String> phone_name;
public static final String NUMBER = "number";
public static final String NUMBER_ID = "number_id";
public static final String PHONE_NAME = "phone_name";
}
注意该类是无法修改,他会随着你实体类变化去自动生成的。
Criteria 查询顺序
- 获得Hibermate的Session对象。
- 以Session对象创建CriteriaBuilder 对象。
- 通过CriteriaBuilder 的createQuery方法,通过泛型去设置查询返回的实例类型。
- 通过CriteriaBuilder 的from方法,指定查询参与的表(实例)
- 通过CriteriaBuilder 的select 去设置查询返回属性。
- 使用CriteriaBuilder 对象的方法创建查询条件(可以是where 子句的条件,也可以是select 子句后面的聚集函数)。
- 执行CriteriaBuilder 的getResultList方法返回结果集。
先睹为快criteria查询
CriteriaBuilder builder = ss.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery(Person.class);
//这里查询的属性 是一个Person 实体,返回的是所有属性
Root<Person> root = criteria.from(Person.class);
//criteria.select(root);这里其实只是查询整个实体,所以select 是不强制有的
criteria.select(root.get((Person_.ID)));//查询指定的属性
criteria.where(builder.equal(root.get(Person_.NAME),"laoqiang"));
java.util.List list = ss.createQuery(criteria).getResultList();
System.out.println(list.size());
Root
CriteriaBuilder builder = ss.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createQuery(Tuple.class);
Root<Person> root = criteria.from(Person.class);
Root<Person> root1 = criteria.from(Person.class);
criteria.multiselect(root,root1);
java.util.List <Tuple> list = ss.createQuery(criteria).getResultList();
System.out.println(list.size());
这段代码两个root 对象跨越两张表,实际上底层操作,是将这两个表进行了笛卡尔积运算。这里可以理解root 控制查询的表的内容,几个表之间查询。
Join
可以通过关联的属性,进行内连接查询。
CriteriaBuilder builder = ss.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery(Person.class);
Root<Person> root = criteria.from(Person.class);
Join<Person,Phone> phoneJoin = root.join(Person_.PHONES);
criteria.select(root);
criteria.where(builder.equal(root.get(Person_.ID), 3));
java.util.List <Person> list = ss.createQuery(criteria).getResultList();
System.out.println(list.size());
Set<Phone> phone = list.get(0).getPhones();
Iterator<Phone> iterator = phone.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next().getPhone_name());
}
对应的数据库语句:
select
person0_.person_id as person_i1_0_,
person0_.name_first as name_fir2_0_,
person0_.name_last as name_las3_0_,
person0_.person_name as person_n4_0_,
person0_.person_gender as person_g5_0_
from
PERSON person0_
inner join
PHONE phones1_
on person0_.person_id=phones1_.person_id
where
person0_.person_id=3
通过join 的关联,无法去获取phone 字段,但是可以通过person 关联字段去获取对应phone 字段。
Fecth
可以通过关联的属性去获取关联实体的字段信息。
CriteriaBuilder builder = ss.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery(Person.class);
Root<Person> root = criteria.from(Person.class);
Fetch<Person,Phone> phoneJoin = root.fetch(Person_.PHONES);
criteria.select(root);
criteria.where(builder.equal(root.get(Person_.ID), 3));
java.util.List <Person> list = ss.createQuery(criteria).getResultList();
底层数据库执行语句:
select
person0_.person_id as person_i1_0_0_,
phones1_.phone_id as phone_id1_1_1_,
person0_.name_first as name_fir2_0_0_,
person0_.name_last as name_las3_0_0_,
person0_.person_name as person_n4_0_0_,
person0_.person_gender as person_g5_0_0_,
phones1_.phone_number as phone_nu2_1_1_,
phones1_.phone_name as phone_na3_1_1_,
phones1_.person_id as person_i4_1_1_,
phones1_.person_id as person_i4_1_0__,
phones1_.phone_id as phone_id1_1_0__
from
PERSON person0_
inner join
PHONE phones1_
on person0_.person_id=phones1_.person_id
where
person0_.person_id=3
查询返回多个属性
两种方式:
CriteriaBuilder builder = ss.getCriteriaBuilder();
//查询多个属性这里一定要指定Object[] 泛型,多个属性的类型不一致
CriteriaQuery<Object[]> criteria = builder.createQuery(Object[].class);
Root<Person> root = criteria.from(Person.class);
javax.persistence.criteria.Path<String> genderPath = root.get(Person_.GENDER);
javax.persistence.criteria.Path<String> namePath = root.get(Person_.NAME);
//path 后面泛型类型是参照你定义该属性的类型
criteria.select(builder.array(genderPath,namePath));//指定查询的多个属性
criteria.where(builder.equal(root.get(Person_.NAME),"laoqiang"));
java.util.List list = ss.createQuery(criteria).getResultList();
System.out.println(list.size());
CriteriaBuilder builder = ss.getCriteriaBuilder();
//查询多个属性这里一定要指定Object[] 泛型,多个属性的类型不一致
CriteriaQuery<Object[]> criteria = builder.createQuery(Object[].class);
Root<Person> root = criteria.from(Person.class);
javax.persistence.criteria.Path<String> genderPath = root.get(Person_.GENDER);
javax.persistence.criteria.Path<String> namePath = root.get(Person_.NAME);
//path 后面泛型类型是参照你定义该属性的类型
criteria.multiselect(genderPath,namePath);//指定查询的多个属性
criteria.where(builder.equal(root.get(Person_.NAME),"laoqiang"));
java.util.List list = ss.createQuery(criteria).getResultList();
System.out.println(list.size());
我们可以构造一个包装类对象,将多个属性存放其中,避免了使用Object数组。
包装类用来封装查询的多个对象
public class PersonWrapper {
private String name;
private String gender;
public PersonWrapper(String name, String gender) {
super();
this.name = name;
this.gender = gender;
}
}
CriteriaBuilder builder = ss.getCriteriaBuilder();
//我们将多个查询属性封装在一个类,作为一个对象,这样就不需要Object[]
CriteriaQuery<PersonWrapper> criteria = builder.createQuery(PersonWrapper.class);
Root<Person> root = criteria.from(Person.class);
javax.persistence.criteria.Path<String> genderPath = root.get(Person_.GENDER);
javax.persistence.criteria.Path<String> namePath = root.get(Person_.NAME);
//path 后面泛型类型是参照你定义该属性的类型
criteria.select(builder.construct(PersonWrapper.class,namePath ,genderPath));//指定查询的多个属性,这里构造的的属性顺序,全好和你定义包装类的构造器一致,以免有问题。
criteria.where(builder.equal(root.get(Person_.NAME),"laoqiang"));
java.util.List list = ss.createQuery(criteria).getResultList();
System.out.println(list.size());
在上面我们通过将多个属性包装成一个对象类,这样其实是有缺点,你还要单独去创建该类,明显就麻烦,下面我们通过Tuple,英文意思:元祖。你数据库查找的某一行是不是就是一元祖,它其实是把多个属性封装在元祖对象中,让你直接用。
CriteriaBuilder builder = ss.getCriteriaBuilder();
//我们将多个查询属性封装在一个类,作为一个对象,这样就不需要Object[]
CriteriaQuery<Tuple> criteria = builder.createQuery(Tuple.class);
Root<Person> root = criteria.from(Person.class);
javax.persistence.criteria.Path<String> genderPath = root.get(Person_.GENDER);
javax.persistence.criteria.Path<String> namePath = root.get(Person_.NAME);
//path 后面泛型类型是参照你定义该属性的类型
criteria.multiselect(namePath,genderPath);
criteria.where(builder.equal(root.get(Person_.NAME),"laoqiang"));
java.util.List <Tuple> list = ss.createQuery(criteria).getResultList();
for(Tuple tuple: list){
System.out.println(tuple.get(namePath));
//在获取值的时候,可以通过path 对象名去获取,也可以通过索引,索引是从0,和构造的顺序一致
System.out.println(tuple.get(0));
System.out.println(tuple.get(genderPath));
System.out.println(tuple.get(1));
}
这种方式是推荐使用的。
查询使用参数
在sql 中可以使用占位符来表示参数,criteria 一样可以,只不过换了一个形式,意思还是一样一样的。
CriteriaBuilder builder = ss.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery(Person.class);
Root<Person> root = criteria.from(Person.class);
criteria.select(root);
ParameterExpression<Integer> personidparameter = builder.parameter(Integer.class);//这里是参数的类型
criteria.where(builder.equal(root.get(Person_.ID), personidparameter));
java.util.List <Person> list = ss.createQuery(criteria).setParameter(personidparameter,3).getResultList();//这里设置参数,两种方式,一个根据索引,一个可以参数名
System.out.println(list.size());
分组
CriteriaBuilder builder = ss.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createQuery(Tuple.class);
Root<Person> root = criteria.from(Person.class);
criteria.groupBy(root.get(Person_.ID));
criteria.multiselect(root.get(Person_.ID),builder.count(root));//这里必须保证分组的字段要在select 子句中有
//Tuple 这里查询必须要是该类型,否则会提示没有该对应的构造器。
criteria.where(builder.equal(root.get(Person_.ID), 3));
java.util.List <Tuple> list = ss.createQuery(criteria).getResultList();
System.out.println(list.size());
本地sql 查询
允许你手写sql 语句,存储过程等,去完成一系列的数据库操作。在本地sql 中写的就是实际数据库的字段和表。
java.util.List<Object[]> person = ss.createNativeQuery("select * from person").getResultList();//这样查询是整个实体属性,因为实体属性不同,所以存放在一个object 数组中
for(Object[] o:person){
System.out.println(o[0]);//这里获取的索引就是和你查询字段位置一致
System.out.println(o[1]);
System.out.println(o[2]);
System.out.println(o[3]);
System.out.println(o[4]);
System.out.println(o[5]);
System.out.println(o[6]);
}
Hibernate将使用java.sql.ResultSetMetadata来推断返回的标量值的实际顺序和类型。
标量查询
频繁的使用ResultSetMetadata 去推测类型会耗费性能,为了减少对ResultSetMetadata依赖去猜测实际返回的查询和类型,我们需要指定查询返回字段的类型。
java.util.List<Object[]> person = ss.createNativeQuery("select person_id,person_name from person")
.addScalar("person_id", IntegerType.INSTANCE).addScalar("person_name",StringType.INSTANCE).getResultList();
for(Object[] o:person){
System.out.println(o[0]);
System.out.println(o[1]);
}
实体查询
hibernate 提供本地sql 查询的结果,可以直接转化为实体的属性。但是使用,需要注意必须是查询的所有属性,select *。
java.util.List<Person> person = ss.createNativeQuery("select * from person").addEntity(Person.class)
.getResultList();
for(int i = 0;i<person.size();i++){
Person p = person.get(i);
System.out.println(p.getName()+p.getGender());
}
可以将查询多个转化成实体,还是注意是查询两个实例的所有,不是部分字段。
java.util.List<Object[]> person = ss.createNativeQuery("select p.*,e.* from person p,phone e where p.person_id = e.person_id").addEntity(Person.class).addEntity(Phone.class)
.getResultList();
for(int i = 0;i<person.size();i++){
Object[] elemets = person.get(i);
Person p = (Person) elemets[0];
System.out.println(p.getName()+p.getGender());
Phone p1 = (Phone) elemets[1];
System.out.println(p1.getNumber()+p1.getPhone_name());
}
p.* 这种是代表某个表中的所有字段。
处理本地关联查询
java.util.List<Object[]> list = ss.createNativeQuery("select * from phone e join person p where p.person_id = e.person_id").addEntity("phone",Phone.class).addJoin("pr111","phone.person")
.getResultList();
for(int i = 0;i<list.size();i++){
Object[] elemets = list.get(i);
Person p = (Person) elemets[1];//经过上述的实体转换,我们在获取的时候需要注意,如果多表查询,例如,本例,如果在获取的时候Person p = (Person) elemets[0],就会发生错误,实体phone 无法转成person,因为在sql 你是通过phone 去调用addjoin ,所以先是phone ,再是person。
System.out.println(p.getName()+p.getGender());
Phone p1 = (Phone) elemets[0];
System.out.println(p1.getNumber()+p1.getPhone_name());
}
在phone 实体中一个Person person 对象引用,Many to one 的关系。在这里使用addEntity和上面的不一样,这里设置的实体,应该是你的主表,而不是关联表。因为转换实体的时候,如果设置关联表,字段会出现重复可能,所以你需要给个名字表示转换后实体名,另外这里虽然涉及到两张表,但是addJoin会通过phone关联属性,自动转换为person 对应的实体,所以不需要手动addEntity person了。 addJoin这个方法是添加关联的属性,第一个参数,是转换后的实体名(这个命名随意),后面的是关联的属性,这里命名就不是随意,必须是你前面实体名.对应关联类的属性,他会根据addEntity的对应实体名,去对应的类找关联的属性。
上面关系是双向的,所以,你也可以通过person 去访问phone。
java.util.List<Object[]> list = ss.createNativeQuery("select * from phone e join person p where p.person_id = e.person_id").addEntity("person",Person.class).addJoin("phone","person.phones")//可以看到该数组第一个是person,第二个才是phone 对象。
.getResultList();
for(int i = 0;i<list.size();i++){
Object[] elemets = list.get(i);
Person p = (Person) elemets[0];
System.out.println(p.getName()+p.getGender());
Phone p1 = (Phone) elemets[1];
System.out.println(p1.getNumber()+p1.getPhone_name());
}
本地sql 查询设置参数
java.util.List<Object[]> list = ss.createNativeQuery("select person_name from person where person_name like :name1 ").setParameter("name1","lao")
.getResultList();
注意虽然用法很像hql,但是又本质区别,它里面可不是字段和对象,而是底层数据库的表、字段。
按照SQL标准的解释,在SQL环境下Catalog和Schema都属于抽象概念,主要用来解决命名冲突问题。
从概念上说,一个数据库系统包含多个Catalog,每个Catalog又包含多个Schema,而每个Schema又包含多个数据库对象(表、视图、序列等),反过来讲一个数据库对象必然属于一个Schema,而该Schema又必然属于一个Catalog,这样我们就可以得到该数据库对象的完全限定名称从而解决命名冲突的问题了