1.需求/目的
- 提供对数据库操作的常用方法
- 隐藏实现细节,使之专注于业务的开发
2.使用环境
- spring boot 2.0.3
- maven
- mysql 5.7
- 引入
<!-- hibernate核心包 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<!-- hibernate 提供用于连接和操作数据库的entitymanager对象 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<!-- 提供数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- 用于将对象的属性封装到对象 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
3.基础方法
- 获取数据库连接
//通过注入的EntityManager来获取数据库操作对象session
@PersistenceContext
private EntityManager entityManager;
//日志对象
protected Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 获取数据库操作对象,,通过session,this.entityManager.unwrap(Session.class);
* @return org.hibernate.Session对象
* @time 2018年9月17日09:11:26
* @author authstr
*/
@Override
public Session getSession() {
return (Session)this.entityManager.unwrap(Session.class);
}
/**
* 刷新Session对象,在进行多个实体的操作时,需要调用该方法
* @time 2018年9月17日09:12:21
* @author authstr
*/
@Override
public void flushSession() {
this.getSession().flush();
this.getSession().clear();
}
4.提供简单增删改操作
- 保存
/**
* 添加/保存一个entity
* @param entity
* @return 主键值
*/
@Override
public Serializable save(Object entity) {
//这里可以根据一些model的设置进行特定的验证或者处理
//比如对象是否符合model接口,设置创建时间之类的
return this.getSession().save(entity);
}
/**
* 添加/保存多个entity
* @param entityList
* @return 主键id的list
*/
@Override
public List<Serializable> saveList(List entityList){
List<Serializable> res=new ArrayList<Serializable>();
for(int i=0;i<entityList.size();i++){
res.add(this.save(entityList.get(i)));
//每20次刷新一下session
if(i%20==0){
this.flushSession();
}
}
return res;
}
- 更新
/**
* 更新/修改一个entity
* @param entity
* @time 2018年9月25日16:15:25
* @author authstr
*/
@Override
public void update(Object entity) {
this.getSession().update(entity);
}
/**
* 更新/修改多个entity
* @param entityList
* @return 修改的数量
* @time 2018年9月25日16:16:01
* @author authstr
*/
@Override
public int updateList(List entityList) {
for(int i=0;i<entityList.size();i++){
update(entityList.get(i));
//每20次刷新一下session
if(i%20==0){
this.flushSession();
}
}
return entityList.size();
}
-删除
/**
* 删除一个entity对象
* @param entity
* @time 2018年11月13日 上午11:24:35
* @author authstr
*/
@Override
public void remove(Object entity) {
this.getSession().remove(entity);
}
5.Query对象的创建
- 在hibernate中需要通过Query对象进行sql语句的执行
- 当前不考虑对Hql的执行,只针对mysql的sql语句
/**
* 创建Query对象
* @param sql sql语句
* @param firstRows 起始数据行
* @param maxRows 获取数据条数
* @param returnType 结果集封装
* @return Query对象
* @time 2019年4月8日14:57:28
* @author authstr
*/
public Query createQuery(String sql,Integer firstRows,Integer maxRows,Class returnType){
Assert.isTrue(StringUtils.hasText(sql),"sql语句必须存在",true);
Assert.isTrue(returnType!=null,"返回值不能为空",true);
NativeQuery query= this.getSession().createNativeQuery(sql);
//设置返回值类型
if(SqlResult.class.equals(returnType)){
//原封不动的封装数据
query.setResultTransformer(SqlResultTransformer.SQL_INSTANCE);
}else{
//将数据封装为HashMap
query.setResultTransformer(MapResultTransformer.MAP_INSTANCE);
}
query.setResultTransformer(new MapResultTransformer());
//设置开始行
if(firstRows!=null){
query.setFirstResult(firstRows);
}
//设置数据条数
if(maxRows!=null){
query.setMaxResults(maxRows);
}
return query;
}
- Assert之前有说明,是一个便捷抛出异常的类
- Query对象同样使用Session创建,方法主要是定义了sql结果集的处理
- 通过定义一个AliasedTupleSubsetResultTransformer的子类,通过Query的setResultTransformer方法,可以设置hibernate对sql结果集的处理逻辑
- 不过这种方法使用泛型比较麻烦,因此这里我不做复杂的处理.直接将结果集封装成hashMap或者原封不动不对数据进行处理
- sql结果集是两个数组,定义一个实体类来进行储存
/**
* 实体类,用于储存sql语句的结果集
* 2019年4月8日14:19:23
* authstr
*/
public class SqlResult {
private String[] aliases;
private Object[] tuple;
public String[] getAliases() {
return aliases;
}
public void setAliases(String[] aliases) {
this.aliases = aliases;
}
public Object[] getTuple() {
return tuple;
}
public void setTuple(Object[] tuple) {
this.tuple = tuple;
}
}
- 将sql结果集原封不动封装成SqlResult对象
/**
* hibernate的转换类,将数据库的返回值封装成SqlResult对象
* @time 2019年4月8日15:21:27
* @author authstr
*
*/
public class SqlResultTransformer extends AliasedTupleSubsetResultTransformer {
public static final SqlResultTransformer SQL_INSTANCE = new SqlResultTransformer();
private Object readResolve() {
return SQL_INSTANCE;
}
public Object transformTuple(Object[] tuple, String[] aliases) {
SqlResult res=new SqlResult();
res.setAliases(aliases);
res.setTuple(tuple);
return res;
}
public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
return false;
}
}
- 将sql结果集封装成hashMap
/**
* hibernate的转换类,将数据库的返回值封装成map
* @author authstr
*
*/
public class MapResultTransformer extends AliasedTupleSubsetResultTransformer {
public static final MapResultTransformer MAP_INSTANCE = new MapResultTransformer();
private Object readResolve() {
return MAP_INSTANCE;
}
public Object transformTuple(Object[] tuple, String[] aliases) {
HashMap<String, Object> result = new HashMap<String, Object>(tuple.length);
for(int i=0;i<aliases.length;i++){
String alias = aliases[i];
if (alias != null) {
result.put(alias, tuple[i]);
}
}
return result;
}
public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
return false;
}
}
6.Query对象的参数设置
- 为了避免sql注入,sql参数的设置都是通过预编译进行的
- hibernate提供了两种参数设置方式
- 一是通过"?"作为占位符的单数组参数
如:select a.* from base_user a where name=?
参数传递:new Object[]{"admin"}
/**
* 通过数组设置查询参数,适用于用”?”来定义参数位置
* @param query 要操作的query对象
* @param values 数组,要替换成的值
* @time 2018年9月25日11:27:58
* @author authstr
*/
public Query setQueryParameters(Query query, Object[] values) {
//数组为空.直接返回
if (!ObjectUtils.isExist(values)) return query;
for(int i=0;i<values.length;i++){
query.setParameter(i + 1, values[i]);
}
return query;
}
这种方式比较方便快捷,缺点是参数较多时,sql语句参数的阅读与修改比较麻烦
- 二是通过":"+参数名称 的方式,使用双数组设置参数
如:select a.* from base_user a where name= :username
参数传递: new String[]{"username"},new Object[]{"admin"}
/**
* 设置query对象的一个参数
* @param query 要操作的query对象
* @param paramName 要替换的sql语句中的占位字符名称,如 :temp
* @param value 要替换成的值
* @return 被操作的query对象
* @time 2018年9月17日09:44:24
* @author authstr
*/
private Query setKeyValue(Query query, String paramName, Object value) {
//如果值是集合类型的对象,如,map,set,list,向下转型并设置
//setParameterList方法无法使用Object作为参数,必须转成正确的类型
if (value instanceof Collection) {
query.setParameterList(paramName, (Collection)value);
} else if (value instanceof Object[]) {
//如果是数组,也向下转型
query.setParameterList(paramName, (Object[])value);
} else {
query.setParameter(paramName, value);
}
return query;
}
/**
* 通过两个数组设置query对象的参数,适用于用”:”+参数名称来定义参数位置
* @param query 要操作的query对象
* @param paramNames 数组,要替换的sql语句中的占位字符名称,如 :temp
* @param values 数组,要替换成的值
* @return 被操作的query对象
* @time 2018年9月17日10:37:32
* @author authstr
*/
public Query setQueryParameters(Query query,String[] paramNames,Object[] values){
//字段名称为空,视为使用”?”来定义参数位置
if(!ObjectUtils.isArrayExist(paramNames)){
return setQueryParameters(query,values);
}
//参数长度验证
Assert.isTrue(paramNames.length==values.length,
"参数长度不一致,parm["+paramNames.length+"],value["+values.length+"]",true);
for(int i=0;i<paramNames.length;i++){
setKeyValue(query, paramNames[i], values[i]);
}
return query;
}
- 一般来说,实际使用时,很少用两个数组来进行":"+参数名称 类型的设置,而是使用Map来保存参数
/**
* 通过Map设置query对象的参数
* @param query query 要操作的query对象
* @param kv key表示要替换的sql语句中的占位字符名称,如 :temp,value表示要替换成的值
* @return 被操作的query对象
* @time 2018年9月17日11:12:24
* @author authstr
*/
public Query setQueryParameters(Query query,Map<String, Object> kv ){
//判断map是否存在
if(!CollectionUtils.isMapExist(kv)){
return query;
}
for (Map.Entry<String, Object> m : kv.entrySet()) {//遍历map
this.setKeyValue(query, m.getKey(), m.getValue());
}
return query;
};
7.执行sql进行查询数据
- sql的执行有4步
1.创建Query对象
2.设置Query参数
3.获取Query查询结果
4.封装查询结果
/**
* 执行查询语句,获取返回值(执行方法)
* @param sql 查询sql语句
* @param fields sql参数 属性项数组
* @param values sql参数 属性值数组
* @param firstRows 查询起始行数
* @param maxRows 查询数据条数
* @param returnType 结果集封装的类型
* @param <T> 结果集类型
* @return 查询结果
* @time 2019年4月5日11:23:53
* @author authstr
*/
public <T> List<T> getBySQL(String sql,String[] fields,Object[] values,Integer firstRows,Integer maxRows,Class<T> returnType){
//获取query对象
Query query=createQuery(sql,firstRows,maxRows,returnType);
//设置参数
setQueryParameters(query,fields,values);
List li=query.list();
List res = converListToModel(returnType, li);
return res;
}
- 对于结果集的封装,使用的是apache.commons.beanutils包.
/**
* 将map的集合,转换为指定对象的集合
* @param clazz 要转换为的对象类型
* @param list 原数据
* @return 转换后的数据
* @time 2018年10月15日 上午11:35:53
* @author authstr
*/
public <T> List converListToModel(Class<T> clazz, List<Map> list){
Assert.isTrue(clazz!=null,"返回类型不能为空",true);
//如果返回值是SqlResult或是Map,不进行处理
if(SqlResult.class.equals(clazz)||HashMap.class.equals(clazz) || Map.class.equals(clazz)){
return list;
}
List<T> res=new ArrayList(list.size());
//检查集合
if(!ObjectUtils.isExist(list)){
return res;
}
//遍历转换
for(Map map:list){
try {
T bean = clazz.newInstance();
BeanUtils.populate(bean, map);
res.add(bean);
} catch (Exception e) {
log.error("无法正确的将sql结果集的某条数据转换为["+clazz.getName()+"]类型的数据;位置:AbstractDao-converListToModel;异常原因:"+e.getMessage());
}
}
-
为了调用查看时方便,除了getBySQL主执行方法,还有一些参数跳转的方法(主要进行方法的跳转,不实际执行)
参数跳转
/**
* 使用 属性+值 的参数方式,执行查询语句,获取返回值(参数跳转)
* @param sql 查询sql语句
* @param kv sql参数,键为属性项数组,值为 属性值数组
* @param firstRows 查询起始行数
* @param maxRows 查询数据条数
* @param returnType 结果集封装的类型
* @param <T> 结果集类型
* @return 查询结果
* @time 2019年4月5日11:27:12
* @author authstr
*/
public <T> List<T> getByMapSQL(String sql,Map<String,Object> kv,Integer firstRows,Integer maxRows,Class<T> returnType){
String[] fields=new String[kv.size()];
Object[] values=new Object[kv.size()];
int i=0;
//遍历Map
for (Entry<String, Object> entry : kv.entrySet()) {
fields[i]= entry.getKey();
values[i]= entry.getValue();
i++;
}
return getBySQL(sql,fields,values,firstRows,maxRows,returnType);
}
/**
* 使用 参数值 的参数方式,执行查询语句,获取返回值(参数跳转)
* @param sql 查询sql语句
* @param values
* @param firstRows 查询起始行数
* @param maxRows 查询数据条数
* @param returnType 结果集封装的类型
* @param <T> 结果集类型
* @return 查询结果
* @return
* @time 2019年4月5日16:53:36
* @author authstr
*/
public <T> List<T> getByValuesSql(String sql,Object[] values,Integer firstRows,Integer maxRows,Class<T> returnType){
return getBySQL(sql,null,values,firstRows,maxRows,returnType);
}
/**
* 执行查询语句,获取返回值(参数跳转)
* @param sql 查询sql语句
* @param fields sql参数 属性项数组
* @param values sql参数 属性值数组
* @param returnType 结果集封装的类型
* @param <T> 结果集类型
* @return 查询结果
* @time 2019年4月5日17:18:59
* @author authstr
*/
public <T> List<T> getBySQL(String sql,String[] fields,Object[] values,Class<T> returnType){
return getBySQL(sql,fields,values,null,null,returnType);
}
- 理论上,getByMapSQL方法使用setQueryParameters(Query query,Map<String, Object> kv )方法进行设置参数比当前进行参数分解后使用setQueryParameters(Query query,String[] paramNames,Object[] values)更高效些.不过这样会写两份执行sql的代码,当前代码还是以可维护性为重,效率在需要的时候针对性修改.
8.执行sql进行修改数据
- 除开查询,有时候也要执行一些删除或者更新的sql语句
- 相对于查询,执行的步骤比较少
/**
* 执行sql语句,对数据库进行操作(map参数)
* @param sql sql语句
* @param kv sql参数
* @return 影响行数
* @time 2018年11月13日 上午11:37:44
* @author authstr
*/
public int executeSQl(String sql,Map<String,Object> kv){
NativeQuery query=getSession().createNativeQuery(sql);
setQueryParameters(query, kv);
return query.executeUpdate();
}
/**
* 执行sql语句,对数据库进行操作(value参数)
* @param sql sql语句
* @param value sql参数
* @return 影响行数
* @time 2018年11月13日 上午11:39:14
* @author authstr
*/
public int executeSQl(String sql,Object[] value){
NativeQuery query=getSession().createNativeQuery(sql);
setQueryParameters(query, value);
return query.executeUpdate();
}
网友评论