DML风格的批量更新和删除
语法格式:
delete | update from? [Where condition]
from是可选的,可以不写。from字句后面只能有一个类名。可以指定别名。
可以使用where字句查询但不能使用连接。
String hqlUpdate = "update User u set name=:newName";
int updateEntities = session.creaateQuery(hqlUpdate).setString("newName","新名字").executeUpdate();
String hqlDelete = "delete User";
int deleteEntities = session.createQuery(hqlDelete).executeUpdate();
HQL查询
- 获取session对象
- 编写HQL语句
- session.createQuery(hql)创建查询对象
- 调用Query.setXXX方法设置查询参数
- 调用Query对象的list()或uniqueResult()返回查询结果。
List p1 = session.createeQuery("select distinct p from Person p
join p.myEvents where title = :eventTitle")
.setString("eventTitle","很普通")
.list();
List p2 = session.createeQuery("select distinct p from Person p
inner join p.myEvents events where event.happenDate between :firstDate and :endDate")
.setDate("firstDate",new Date())
.setDate("lastDate",new Date())
.list();
条件查询
- Creteria:代表依次查询
- Criterion:代表一个查询条件
- Restrictions:产生查询条件的工具类。
执行条件查询的步骤:
- 获得session
- session创建criteria对象。
- 使用Restrictions的静态方法创建Criterion对象。
- 向Criteria对象添加Criterion查询条件。
5 执行Criteria的list()或uniqueResult()。
List list = session.createCriteria(Student.class)
.add(Restrictions.gt("name","a"))
.list():
Cretiria还有如下方法:
setFirstResult(int);
setMaxResult(int);
addOrder(Order order)
分组、投影、聚合
使用Projection代表投影运算。Projection是一个接口,而Projections作为Projection的 工厂。负责生产Projection对象。
List cats = session.createCriteria(Cat.class)
.setProjection(Projections.projectionList()
.add(Projection.rowCOunt())
.add(Projection.avg("weight"))
.add(Projection.groupProperty("color"))
.addOrder(Order.asc("color"))
)
.list();
离线查询和子查询
使用DetachedCriteria实现。
//离线查询
DetachedCriteria query = DetachedCriteria
.forClass(Student.class)
.setProjection(Property.forName("name"));
Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();
List list = query.getExecutableCriteria(session)
.list();
//子查询
List list = session.createCriteria(Student.class)
//下面两种方式都行
.add( Property.forName("name").in(query))
// .add( Subqueries.propertyIn("name" , query))
.list();
SQL查询
通过Query的子接口SQLQuery.它多了两个方法:
addEntity():将查询到的记录与特定的实体相关联。
addScalar():将查询到的记录关联成标量值。
//明确指定查询中返回的数据列返回的类型
session.createSQLQeury("select * from student_inf")
.addScalar("name",StandardBasicTypes.STRING)
.list();
//实体查询
String sql = "select * from enroment_inf where year=?1";
List list = session.createSQLQuery(sql)
.addEntity(Enrolment.class)
.setInteger("1",2005)
.list();
//映射多个实体
String sqlString = "select s.*,e.*,c.* "
+ "from student_inf s,enrolment_inf e,course_inf c "
+ "where s.student_id = e.student_id "
+ "and e.course_code = c.course_code";
List list = session.createSQLQuery(sqlString)
.addEntity("s", Student.class)
.addEntity("e", Enrolment.class)
.addEntity("c", Course.class)
.list();
for (Object ele : list)
{
Object[] objs = (Object[])ele;
Student s = (Student)objs[0];
Enrolment e = (Enrolment)objs[1];
Course c = (Course)objs[2];
System.out.println(s.getName() + "\t"
+ e.getYear() + "\t" + e.getSemester()
+ "\t" + c.getName());
}
还可将查询结果转换成非持久化类。(javabean).Query接口有个setResultTransformer()方法。传进一个Transformers对象即可转换。
String sqlString = "select s.name stuName, c.name courseName "
+ "from student_inf s,enrolment_inf e,course_inf c "
+ "where s.student_id = e.student_id "
+ "and e.course_code = c.course_code ";
List list = session.createSQLQuery(sqlString)
.setResultTransformer(Transformers
.aliasToBean(StudentCourse.class))
.list();
//StudnetCourse只有两个属性stuName,courseName。
处理关联和映射
将关联实体转换成查询结果
SQLQuery.addJoin(String alias,String property):第一个是转换后的实体名,第二个是待转换的实体属性。
String sqlString = "select s.* , e.* from student_inf s , "
+ "enrolment_inf e where s.student_id=e.student_id";
List list = session.createSQLQuery(sqlString)
.addEntity("s", Student.class)
.addJoin("e" , "s.enrolments")
.list();
for (Object ele : list)
{
Object[] objs = (Object[])ele;
Student s = (Student)objs[0];
Enrolment e = (Enrolment)objs[1];
System.out.println(s.getName() + "\t" + e.getYear());
}
命名sql查询
可以使用@NamedNativeQuery来定义原生sql查询。有如下属性:
name:查询名称
query:sql语句
resultClass:查询映射的实体类的类名
resultSetMapping:指定一个sql结果映射
如果需要使用resultresultSetMapping属性。则需要使用@SqlResultSetMapping定义sql结果映射。
将查询得到的结果转换为标量查询或实体查询。
@SqlResultSetMapping有以下属性:
name:
columns:值为@ColumnResult注解数组。每个@ColumnResult定义一个标量查询。
entities:值为@EntityResult注解数组。每个@EntityResult定义一个实体查询
classes:值为@ConstructorResult注解数组。每个@ConstructorResult负责将指定的多列转换为javabean的对应属性。
@NamedNativeQueries({
@NamedNativeQuery(name="simpleQuery"
, query="select s.student_id , s.name from student_inf s"
, resultClass=Student.class),
@NamedNativeQuery(name="queryTest"
, query="select s.*,e.*,c.* from student_inf s,enrolment_inf e,"
+ " course_inf c where s.student_id = e.student_id and"
+ " e.course_code = c.course_code and e.year=:targetYear"
, resultSetMapping = "firstMapping"),
@NamedNativeQuery(name="callProcedure"
, query="{call select_all_student()}"
, resultSetMapping = "secondMapping")
})
@SqlResultSetMappings({
@SqlResultSetMapping(name="firstMapping"
, entities={@EntityResult(entityClass=Student.class),
@EntityResult(entityClass=Enrolment.class),
@EntityResult(entityClass=Course.class , fields=
{
@FieldResult(name="courseCode" , column="c.course_code"),
@FieldResult(name="name" , column="c.name")
})
}
, columns={@ColumnResult(name="s.name" , type=String.class)}
),
@SqlResultSetMapping(name="secondMapping"
, entities={@EntityResult(entityClass=Student.class , fields=
{
@FieldResult(name="studentNumber" , column="student_id"),
@FieldResult(name="name" , column="name")
})
})
})
//调用
List list = session.getNamedQuery("queryTest")
.setInteger("targetYear" , 2005)
.list();
for(Object ele : list)
{
Object[] objs = (Object[])ele;
Student s = (Student)objs[0];
Enrolment e = (Enrolment)objs[1];
Course c = (Course)objs[2];
String stuName = (String)objs[3];
System.out.println(s.getName() + "\t"
+ e.getYear() + "\t" + e.getSemester()
+ "\t=" + e.getCourse().getName() + "=\t" + stuName);
}
调用存储过程
可以通过命名sql查询来调用存储过程或函数。对于函数,该函数必须返回一个结果集。对于存储过程,该存储过程的第一个参数必须是参数,且其数据类型必须是结果集。
//创建一个简单的存储过程
create procedure select_all_student()
select * from student_inf;
//定义一个调用存储过程的命名sql
@NameNativeQuery(name="callProcedure"
,query="{call select_all_student()}"
,resultSetMapping="secondMapping")
@SqlResultSetMapping(name="secondMapping"
,entities={@EntityResult(entityClass=Student.class,fileds=
{
@FieldResult(name="studentNumber",column="student_id"),
@FieldResult(name="name",column="name")
})
})
//调用存储过程
List list = session.getNamedQuery("callProcedure")
.list();
使用定制sql
不使用hibernate提供的默认sql语句来执行。而使用自己写的。
如果想要调用存储过程来执行删除,更新等操作。则需指定callable=true.
@SQLInsert(callable=true,sql="{call createPerson(?,?)}")
@SQLUpdate(callable=true,sql="{? = call UpdatePerson(?)}")
@SQLDelete(callable=true,sql="{? = call deletePerson(?,?)}")
还可用命名sql实现定制装载。例如在装载News时为title属性的前后增加三个等号。
@SQLInsert(sql="insert into news_inf(content , title) values(upper(?), ?)")
@SQLUpdate(sql="update news_inf set content=upper(?), title=? where news_id=?")
@SQLDelete(sql="delete from news_inf where news_id=?")
@SQLDeleteAll(sql="delete from news_inf")
@Loader(namedQuery = "news_loader")
@NamedNativeQuery(name="news_loader"
, query="select news_id , concat('===' , concat(title , '===')) as title"
+ " , content from news_inf n where news_id=?"
, resultClass = News.class)
@Entity
@Table(name="news_inf")
public class News
{
@Id @Column(name="news_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String title;
private String content;
数据过滤
过滤器与持久化类的@Where注解非常相似。他们的区别是过滤器可以带参数,运用程序可以在运行时决定是否启动指定的过滤器。使用怎样的参数值。而@Where注解将一直生效。且无法动态传入参数。
使用过滤器的三个步骤:
1. 定义过滤器,使用hibernate提供的@FilterDef注解定义过滤器。如果需要定义多个过滤器则需要使用@FilterDefs注解组合多个@FilterDef.
2. 使用@Filter元素应用过滤器
3. 在代码中通过session启用过过滤器。
@FilterDef属性:
name:
defaultCondition:默认过滤条件。可不写。
parameters:指定过滤器中sql表达式支持的参数。
@FilterDefs({
@FilterDef(name="effectiveDate"
, parameters={@ParamDef(name="asOfDate" , type="date")}),
@FilterDef(name="category"
, parameters={@ParamDef(name="catId" , type="int")})
})
@Entity
@Table(name="product_inf")
@Filter(name="effectiveDate"
, condition=":asOfDate BETWEEN eff_start_date AND eff_end_date")
public class Product
{
@Id @Column(name="product_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column(name="product_name")
private String name;
@Column(name="stock_number")
private int stockNumber;
@Column(name="eff_start_date")
private Date effectiveStartDate;
@Column(name="eff_end_date")
private Date effectiveEndDate;
@ManyToMany(targetEntity=Category.class)
@JoinTable(name="product_category"
, joinColumns=@JoinColumn(name="product_id")
, inverseJoinColumns=@JoinColumn(name="category_id"))
@Filters({
@Filter(name="effectiveDate"
, condition=":asOfDate BETWEEN eff_start_date and eff_end_date"),
@Filter(name="category"
, condition="category_id = :catId")
})
private Set<Category> categories
= new HashSet<>();
系统默认不启动过滤器,必须通过session.enableFilteer(String filterName)才可以启用,而且一旦启动他将在整个session内有效。知道session调用disableFilter()方法。
session.enableFilter("effectiveDate")
.setParameter("asOfDate", new Date());
session.enableFilter("category")
.setParameter("catId", 2);
List list = session.createQuery("from Product as p").list();
for (Object obj : list)
{
Product p = (Product)obj;
System.out.println(p.getName());
System.out.println("----" + p.getCategories());
}
拦截器
通过Interceptor接口,可以从session中回调应用程序的特定方法。这种回调机制可以让应用程序在持久化对象被保存、删除、修改、加载之前,检查并修改其属性。
使用步骤:
1. 定义实现Interceptor接口的拦截器。最好继承EmptyInterceptor类。
public class MyInterceptor extends EmptyInterceptor
{
private int updates;
private int creates;
public void onDelete(Object entity , Serializable id ,
Object[] state , String[] propertyNames , Type[] types)
{
// do nothing
}
public boolean onFlushDirty(Object entity , Serializable id ,
Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types)
{
updates++;
for ( int i = 0; i < propertyNames.length; i++ )
{
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) )
{
currentState[i] = new Date();
return true;
}
}
return false;
}
public boolean onLoad(Object entity , Serializable id ,
Object[] state,String[] propertyNames,Type[] types)
{
for ( int i = 0; i < propertyNames.length ; i++ )
{
if ( "name".equals( propertyNames[i] ) )
{
System.out.println( state[i] );
return true;
}
}
return false;
}
public boolean onSave(Object entity , Serializable id ,
Object[] state,String[] propertyNames,Type[] types)
{
creates++;
for ( int i = 0; i < propertyNames.length; i++ )
{
if ("createTimestamp".equals( propertyNames[i]))
{
state[i] = new Date();
return true;
}
}
return false;
}
public void postFlush(Iterator entities)
{
System.out.println("´创建的次数"
+ creates + ", 更新的次数" + updates);
}
public void preFlush(Iterator entities)
{
// do nothing
}
public void beforeTransactionCompletion(Transaction tx)
{
System.out.println("事务即将开始");
}
public void afterTransactionCompletion(Transaction tx)
{
System.out.println("事务已将结束");
}
- 通过sessionFactory.openSession(Interceptor in)启动局部拦截器。或Configuration.setInterceptor(Interceptor in)来设置全局拦截器。
static Configuration cfg = new Configuration()
.configure()
.setInterceptor(new MyInterceptor());
事件系统
可作为拦截器的补充或代替拦截器。
Session接口的每个方法都有对应的事件。比如LoadEvent,FlushEvent等。当session调用某个方法时,session会生成对应的事件。并激活对应的事件监听器。
可以在系统中实现并注册LoadEventListener监听器,负责所有的调用session.load()方法的请求。
使用事件系统的步骤:
1. 实现自己的事件监听器类。一般采用继承系统默认的监听器,扩展特定的方法。
2. 注册自定义事件监听器,代替系统默认的事件监听器。
public class MySaveListener extends DefaultSaveEventListener
{
public Serializable performSaveOrUpdate(SaveOrUpdateEvent event)
{
System.out.println(event.getObject());
return super.performSaveOrUpdate(event);
}
}
public class MyLoadListener extends DefaultLoadEventListener
{
public void onLoad(LoadEvent event,
LoadEventListener.LoadType loadType)
throws HibernateException
{
System.out.println(event.getEntityClassName()
+ "==========" + event.getEntityId());
super.onLoad(event, loadType);
}
}
hibernate提供了一个EventListenerRegistry接口来注册事件监听器。一共有三个方法:
appendListener();加到系统默认的事件监听器后面
prependListener():加到系统默认的事件监听器前面
setListener():替换系统默认的事件监听器
static ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.applySettings(cfg.getProperties()).build();
static SessionFactory sf = cfg.buildSessionFactory(serviceRegistry);
static{
EventListenerRegistry elr = ((SessionFactoryImpl)sf)
.getServiceRegistry().getService(EventListenerRegistry.class);
elr.setListeners(EventType.SAVE, MySaveListener.class);
elr.setListeners(EventType.LOAD, MyLoadListener.class);
}