Using native SQL queries
当需要复杂查询时,直接写SQL必不可少。
Automatic resultset handling
Hibernate可以自动将SQL的查询结果封装成实体对象:
List result = session.createSQLQuery("select * from CATEGORY")
.addEntity(Category.class)
.list();
进一步,当有表连接时,指定查询某个实体:
session.createSQLQuery("select {i.*} from ITEM i" +
" join USERS u on i.SELLER_ID = u.USER_ID" +
" where u.USERNAME = :uname")
// 指定SQL查询结果要映射成哪个实体对象
.addEntity("i", Item.class)
.setParameter("uname", "johndoe");
查询实体并初始化(eager fetch)关联实体或collection:
session.createSQLQuery("select {i.*}, {u.*} from ITEM i" +
" join USERS u on i.SELLER_ID = u.USER_ID" +
" where u.USERNAME = :uname")
.addEntity("i", Item.class)
.addJoin("u", "i.seller")
.setParameter("uname", "johndoe");
addJoin()
可解释为:i.seller
关联属性将用别名为u
的查询结果来立即初始化。
Retrieving scalar values
标量查询
// 返回结果为: a List of Object[],数组中ITEM表的每一个字段
List result = session.createSQLQuery("select * from ITEM").list();
// 通过addScalar()指定scalar value
session.createSQLQuery("select u.FIRSTNAME as fname from USERS u")
.addScalar("fname");
下例将字符串转成枚举类型:
Properties params = new Properties();
params.put("enumClassname", "auction.model.Rating");
session.createSQLQuery("select c.RATING as rating from COMMENTS c where c.FROM_USER_ID = :uid")
.addScalar("rating", Hibernate.custom(StringEnumUserType.class, params))
.setParameter("uid", new Long(123));
package auction.model;
public enum Rating {
EXCELLENT, OK, BAD;
}
@Entity
@Table(name = "COMMENT")
public class Comment implements Serializable, Comparable {
@Id @GeneratedValue
@Column(name = "COMMENT_ID")
private Long id = null;
@Enumerated(EnumType.STRING)
@Column(name = "RATING", nullable = false, updatable = false)
private Rating rating;
}
public class StringEnumUserType implements EnhancedUserType, ParameterizedType {
private Class<Enum> enumClass;
public void setParameterValues(Properties parameters) {
String enumClassName = parameters.getProperty("enumClassname");
try {
enumClass = ReflectHelper.classForName(enumClassName);
}
catch (ClassNotFoundException cnfe) {
throw new HibernateException("Enum class not found", cnfe);
}
}
public Class returnedClass() {
return enumClass;
}
public int[] sqlTypes() {
return new int[] { Hibernate.STRING.sqlType() };
}
public boolean isMutable() {
return false;
}
public Object deepCopy(Object value) {
return value;
}
public Serializable disassemble(Object value) {
return (Enum) value;
}
public Object replace(Object original, Object target, Object owner) {
return original;
}
public Object assemble(Serializable cached, Object owner) {
return cached;
}
public boolean equals(Object x, Object y) {
return x==y;
}
public int hashCode(Object x) {
return x.hashCode();
}
public Object fromXMLString(String xmlValue) {
return Enum.valueOf(enumClass, xmlValue);
}
public String objectToSQLString(Object value) {
return '\'' + ( (Enum) value ).name() + '\'';
}
public String toXMLString(Object value) {
return ( (Enum) value ).name();
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws SQLException {
String name = rs.getString( names[0] );
return rs.wasNull() ? null : Enum.valueOf(enumClass, name);
}
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws SQLException {
if (value==null) {
st.setNull(index, Hibernate.STRING.sqlType());
}
else {
st.setString( index, ( (Enum) value ).name() );
}
}
}
具体请下载原书代码。
最后,同时查询标量和实体对象:
// 返回:a collection of Object[],数组中一个是Item对象,一个是字符串
session.createSQLQuery("select {i.*}, u.FIRSTNAME as fname from ITEM i"
+ " join USERS u on i.SELLER_ID = u.USER_ID"
+ " where u.USERNAME = :uname")
.addEntity("i", Item.class) // 实体
.addScalar("fname") // 标量
.setParameter("uname", "johndoe");
Native SQL in Java Persistence
JPA使用createNativeQuery()
创建SQL查询。
A native SQL query may return entity instances, scalar values, or a mix of both.
JPA如果要自动将SQL查询结果封装成实体对象,则必须查出实体所对应表的所有字段:
// 返回 a collection of Category instances
entityManager.createNativeQuery("select * from CATEGORY", Category.class);
如果只是查询实体的某几个属性呢?
entityManager.createNativeQuery("select " +
"i.ITEM_ID as ITEM_ID, i.ITEM_PRICE as ITEM_PRICE, " +
"u.USERNAME as USER_NAME, u.EMAIL as USER_EMAIL " +
"from ITEM i join USERS u on i.SELLER_ID = u.USER_ID",
"ItemSellerResult");
需要@FieldResult
来映射 实体属性和查询结果,以下映射信息放到实体中即可。
@SqlResultSetMapping(
name = "ItemSellerResult",
entities = {
@EntityResult(
entityClass = auction.model.Item.class,
fields = {
@FieldResult(name = "id", column = "ITEM_ID"),
@FieldResult(name = "initialPrice", column = "ITEM_PRICE") }),
@EntityResult(
entityClass = auction.model.User.class,
fields = {
@FieldResult(name = "username", column = "USER_NAME"),
@FieldResult(name = "email", column = "USER_EMAIL") })
}
)
上面SQL的查询结果是a collection of Object[],数组中第一个是Item对象,第二个是User对象。
如果查询的是标量scalar value:
entityManager.createNativeQuery("select " +
"i.ITEM_ID as ITEM_ID, count(b.*) as NUM_OF_BIDS " +
"from ITEM i join BIDS b on i.ITEM_ID = b.ITEM_ID " +
"group by ITEM_ID",
"ItemBidResult");
需要通过@ColumnResult
来指定查询结果中的列名:
@SqlResultSetMapping(
name = "ItemBidResult",
columns = {
@ColumnResult(name = "ITEM_ID"),
@ColumnResult(name = "NUM_OF_BIDS")
}
)
上面SQL的查询结果依然是a collection of Object[],数组中是两个数值型值。
当使用SQL来查询,总是希望能将查询结果,自动封装成业务对象,而不是通过硬编码将查询结果赋值给业务对象。对于JPA可以使用@EntityResult
(参考这个例子),对于Hibernate可以使用ResultTransformer
来完成自动转换。
sqlQuery.setResultTransformer( Transformers.aliasToBean(ItemDTO.class) );
此文是对《Java Persistence with Hibernate》第15章SQL部分的归纳。