场景:
角色[1]-----[*](资源[1]---[*]权限)
某个角色 具有 某个资源的 某些权限,当然此处都是多对多 为了好理解 暂时1---*。 这里是资源-对应-多个权限,但是权限一般不会很多,而且我们一般也不会根据权限去查找,因此没必要做个关联表,此处我们可以使用字符串如1,2,3,4来存储其id,这样可以有效减少中间表数量 提高效率。
方案:
如果不想在程序中拼接这种字符串 我们可以考虑使用Hibernate自定义数据类型; 即把集合类型--->某个分隔符连接的字符串
/**
* Copyright (c) 2005-2012 https://github.com/zhangkaitao
*
* Licensed under the Apache License, Version 2.0 (the "License");
*/
package com.sishuok.es.common.repository.hibernate.type;
import com.google.common.collect.Lists;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.EnhancedUserType;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import java.io.*;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
/**
* 将List转换为指定分隔符分隔的字符串存储 List的元素类型只支持常见的数据类型 可参考{@link org.apache.commons.beanutils.ConvertUtilsBean}
* <p>User: Zhang Kaitao
* <p>Date: 13-4-16 上午8:32
* <p>Version: 1.0
*/
public class CollectionToStringUserType implements UserType, ParameterizedType, Serializable {
/**
* 默认,
*/
private String separator;
/**
* 默认 java.lang.Long
*/
private Class elementType;
/**
* 默认 ArrayList
*/
private Class collectionType;
@Override
public void setParameterValues(Properties parameters) {
String separator = (String)parameters.get("separator");
if(!StringUtils.isEmpty(separator)) {
this.separator = separator;
} else {
this.separator = ",";
}
String collectionType = (String)parameters.get("collectionType");
if(!StringUtils.isEmpty(collectionType)) {
try {
this.collectionType = Class.forName(collectionType);
} catch (ClassNotFoundException e) {
throw new HibernateException(e);
}
} else {
this.collectionType = java.util.ArrayList.class;
}
String elementType = (String)parameters.get("elementType");
if(!StringUtils.isEmpty(elementType)) {
try {
this.elementType = Class.forName(elementType);
} catch (ClassNotFoundException e) {
throw new HibernateException(e);
}
} else {
this.elementType = Long.TYPE;
}
}
@Override
public int[] sqlTypes() {
return new int[] {Types.VARCHAR};
}
@Override
public Class returnedClass() {
return collectionType;
}
@Override
public boolean equals(Object o, Object o1) throws HibernateException {
if (o == o1) {
return true;
}
if (o == null || o == null) {
return false;
}
return o.equals(o1);
}
@Override
public int hashCode(Object o) throws HibernateException {
return o.hashCode();
}
/**
* 从JDBC ResultSet读取数据,将其转换为自定义类型后返回
* (此方法要求对克能出现null值进行处理)
* names中包含了当前自定义类型的映射字段名称
* @param names
* @param owner
* @return
* @throws HibernateException
* @throws java.sql.SQLException
*/
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
String valueStr = rs.getString(names[0]);
if(StringUtils.isEmpty(valueStr)) {
return null;
}
String[] values = StringUtils.split(valueStr, separator);
Collection result = newCollection();
for(String value : values) {
result.add(ConvertUtils.convert(value, elementType));
}
return result;
}
private Collection newCollection() {
try {
return (Collection)collectionType.newInstance();
} catch (Exception e) {
throw new HibernateException(e);
}
}
/**
* 本方法将在Hibernate进行数据保存时被调用
* 我们可以通过PreparedStateme将自定义数据写入到对应的数据库表字段
*/
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
String valueStr;
if(value == null) {
valueStr = "";
} else {
valueStr = StringUtils.join((Collection)value, separator);
}
st.setString(index, valueStr);
}
/**
* 提供自定义类型的完全复制方法
* 本方法将用构造返回对象
* 当nullSafeGet方法调用之后,我们获得了自定义数据对象,在向用户返回自定义数据之前,
* deepCopy方法将被调用,它将根据自定义数据对象构造一个完全拷贝,并将此拷贝返回给用户
* 此时我们就得到了自定义数据对象的两个版本,第一个是从数据库读出的原始版本,其二是我们通过
* deepCopy方法构造的复制版本,原始的版本将有Hibernate维护,复制版由用户使用。原始版本用作
* 稍后的脏数据检查依据;Hibernate将在脏数据检查过程中将两个版本的数据进行对比(通过调用
* equals方法),如果数据发生了变化(equals方法返回false),则执行对应的持久化操作
*
* @param o
* @return
* @throws HibernateException
*/
@Override
public Object deepCopy(Object o) throws HibernateException {
if(o == null) return null;
Collection copyCollection = newCollection();
copyCollection.addAll((Collection)o);
return copyCollection;
}
/**
* 本类型实例是否可变
* @return
*/
@Override
public boolean isMutable() {
return true;
}
/* 序列化 */
@Override
public Serializable disassemble(Object value) throws HibernateException {
return ((Serializable)value);
}
/* 反序列化 */
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return cached;
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
}
1、setParameterValues 作用是参数化 集合类型 和分隔符 不写死了
2、deepCopy必须复制一份 否则即使我们改了 session也检测不到脏数据
使用:
/**
* 此处没有使用关联 是为了提高性能(后续会挨着查询资源和权限列表,因为有缓存,数据量也不是很大 所以性能不会差)
* <p>User: Zhang Kaitao
* <p>Date: 13-4-5 下午2:04
* <p>Version: 1.0
*/
@TypeDef(
name = "SetToStringUserType",
typeClass = CollectionToStringUserType.class,
parameters = {
@Parameter(name = "separator", value = ","),
@Parameter(name = "collectionType", value = "java.util.HashSet"),
@Parameter(name = "elementType", value = "java.lang.Long")
}
)
@Entity
@Table(name = "sys_role_resource_permission")
public class RoleResourcePermission extends BaseEntity<Long> {
/**
* 角色id
*/
@ManyToOne(optional = true, fetch = FetchType.EAGER)
@Fetch(FetchMode.SELECT)
private Role role;
/**
* 资源id
*/
@Column(name ="resource_id")
private Long resourceId;
/**
* 权限id列表
* 数据库通过字符串存储 逗号分隔
*/
@Column(name ="permission_ids")
@Type(type = "SetToStringUserType")
private Set<Long> permissionIds;
public RoleResourcePermission() {
}
@TypeDef(
name = "SetToStringUserType",
typeClass = CollectionToStringUserType.class,
parameters = {
@Parameter(name = "separator", value = ","),
@Parameter(name = "collectionType", value = "java.util.HashSet"),
@Parameter(name = "elementType", value = "java.lang.Long")
}
)
定义类型并指定参数化的集合类型、元素类型和分隔符。