前面讲了如何使用,该讲讲如何实现了。
其实技术上并没有什么难度,主要就是一个设计思路。
先来讲解一下superDao,大体上跟前面实现的原理没有什么变化,主要就是添加了根据class对象查询及组合使用。
先来看一下superDao的使用声明:
<bean id="superDao" class="com.mincoder.speed.persistence.SuperDaoImpl">
<!--必须-->
<property name="jdbcTemplate" ref="jdbcTemplate"/>
<!--可选,默认使用com.mincoder.speed.persistence.NameHandler-->
<property name="nameHandler" ref="defaultNameHandler"/>
<!--可选,默认使用spring提供的org.springframework.jdbc.core.BeanPropertyRowMapper-->
<property name="rowMapperClass" value="com.mincoder.test.SpeedRowMapper"/>
<!--可选,默认true-->
<property name="isNativeId" value="true"/>
</bean>
如果你的操作都是默认的,并且spring配置了扫描superDao的包路径,就无须在xml配置文件中声明了。
jdbcTemplate,属性是必须的,不多说了。
nameHandler,不配置默认处理以下划线分隔的方式,例如 USER_ID* 对应 userId,*PARENT_USER_ID 对应 parentUserId。
rowMapperClass,不配置默认使用spring提供的BeanPropertyRowMapper.newInstance(clazz)方法,同样可以处理以下划线分隔的方式。
isNativeId,不配置默认数据库主键是自增类型,适用于mysql、sql server等,oracle序列类型的不适用。
superDaoImpl需要有对应以上配置的变量,并且还要保存一些操作信息:
/**
* 通用dao实现
*
* Created by liyd on 6/26/14.
*/
@Repository
public class SuperDaoImpl implements SuperDao {
/** 排序 */
private static final ThreadLocal<String> LOCAL_ORDER_BY = new ThreadLocal<String>();
/** 白名单 */
private static final ThreadLocal<List<String>> INCLUDE_FIELDS = new ThreadLocal<List<String>>();
/** 黑名单 */
private static final ThreadLocal<List<String>> EXCLUDE_FIELDS = new ThreadLocal<List<String>>();
/** 操作的字段 */
private static final ThreadLocal<List<AutoField>> AUTO_FIELDS = new ThreadLocal<List<AutoField>>();
/** spring jdbcTemplate 对象 */
@Autowired
protected JdbcOperations jdbcTemplate;
/** 名称处理器,为空按默认执行 */
@Autowired(required = false)
protected NameHandler nameHandler;
/** rowMapper,为空按默认执行 */
@Autowired(required = false)
protected String rowMapperClass;
@Autowired(required = false)
protected Boolean isNativeId = true;
......
}
一些操作的信息肯定需要先保存起来,那么保存在哪里呢?dao肯定会有多个线程同时调用并发的情况,最佳答案当然是ThreadLocal了。
下面是每个变量的说明:
LOCAL*ORDER*BY:保存查询排序信息
INCLUDE_FIELDS:保存白名单信息
EXCLUDE_FIELDS:保存黑名单信息
AUTO_FIELDS:保存操作的字段信息
jdbcTemplate:不多说了,spring JdbcTemplate对象
nameHandler:名称处理类,方便扩展,可以在配置文件中替换默认
rowMapperClass:rowMapper处理类,方便扩展,可以在配置文件中替换默认
isNativeId:主键生成策略,是否自增,可以在配置文件中指定,默认true
看了上面的变量,对于superDao的实现流程相信已经心里有数了吧!
nameHandler和rowMapperClass这里不细说了,将在稍后做具体说明,其实前面的文章已经有说明了,主要说下改动的地方。
以下列举几个典型的方法实现。
insert方法
/**
* 插入数据
*
* @param entity the entity
* @param clazz the clazz
* @return long
*/
private Long insert(Object entity, Class<?> clazz) {
NameHandler handler = this.getNameHandler();
Long id = null;
if (!this.isNativeId) {
String className = (entity == null ? clazz.getSimpleName() : entity.getClass()
.getSimpleName());
String primaryValueFrag = handler.getPrimaryValueFrag(className, this.isNativeId);
id = jdbcTemplate.queryForLong(primaryValueFrag);
String primaryName = handler.getPrimaryName(className);
this.set(NameUtils.getCamelName(primaryName), id);
}
final BoundSql boundSql = SqlUtils.buildInsertSql(entity, clazz, this.getFieldMap(),
this.getNameHandler());
if (this.isNativeId) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection con)
throws SQLException {
PreparedStatement ps = con.prepareStatement(boundSql.getSql(),
new String[] { boundSql.getPrimaryKey() });
int index = 0;
for (Object param : boundSql.getParams()) {
index++;
ps.setObject(index, param);
}
return ps;
}
}, keyHolder);
id = keyHolder.getKey().longValue();
} else {
jdbcTemplate.update(boundSql.getSql(), boundSql.getParams().toArray());
}
return id;
}
@Override
public Long insert(Object entity) {
return this.insert(entity, null);
}
@Override
public Long insert(Class<?> clazz) {
return this.insert(null, clazz);
}
insert方法的实现,比较复杂一点的地方就是主键的生成策略,是数据库自增型还是序列等方式指定型。
主要就是根据isNativeId判断在插入数据时是否生成主键返回,如果是oracle等序列方式的,就在AUTO_FIELDS字段中set一个主键属性。
set方法:
@Override
public SuperDao set(String fieldName, Object value) {
this.addAutoFields(fieldName, null, "=", AutoField.UPDATE_FIELD, value);
return this;
}
其它就是sql语句的拼装了。
BoundSql buildInsertSql方法:
/**
* 构建insert语句
*
* @param entity 实体映射对象
* @param clazz the clazz
* @param fieldMap the field map
* @param nameHandler 名称转换处理器
* @return bound sql
*/
public static BoundSql buildInsertSql(Object entity, Class<?> clazz,
Map<String, Object> fieldMap, NameHandler nameHandler) {
Class<?> entityClass = (clazz == null ? entity.getClass() : clazz);
String tableName = nameHandler.getTableName(entityClass.getSimpleName());
String primaryName = nameHandler.getPrimaryName(entityClass.getSimpleName());
List<AutoField> autoFields = (List<AutoField>) fieldMap.get(AUTO_FIELDS);
List<AutoField> allAutoField = getAllAutoField(entity, autoFields, INSERT);
StringBuilder sql = new StringBuilder("INSERT INTO ");
List<Object> params = new ArrayList<Object>();
sql.append(tableName);
sql.append("(");
StringBuilder args = new StringBuilder();
args.append("(");
for (AutoField autoField : allAutoField) {
sql.append(nameHandler.getColumnName(autoField.getName()));
args.append("?");
params.add(autoField.getValues()[0]);
sql.append(",");
args.append(",");
}
sql.deleteCharAt(sql.length() - 1);
args.deleteCharAt(args.length() - 1);
args.append(")");
sql.append(")");
sql.append(" VALUES ");
sql.append(args);
return new BoundSql(sql.toString(), primaryName, params);
}
queryList方法:
@Override
public <T> List<T> queryList(Class<T> clazz) {
BoundSql boundSql = SqlUtils.buildListSql(null, clazz, this.getFieldMap(),
this.getNameHandler());
List<?> list = jdbcTemplate.query(boundSql.getSql(), boundSql.getParams().toArray(),
this.getRowMapper(clazz));
return (List<T>) list;
}
@Override
public <T> List<T> queryList(T entity) {
BoundSql boundSql = SqlUtils.buildListSql(entity, null, this.getFieldMap(),
this.getNameHandler());
List<?> list = jdbcTemplate.query(boundSql.getSql(), boundSql.getParams().toArray(),
this.getRowMapper(entity.getClass()));
return (List<T>) list;
}
queryList的查询逻辑比较简单,但是拼装sql会复杂一点,因为要处理白名单、黑名单及排序等。
SqlUtils.buildListSql方法:
/**
* 构建列表查询sql
*
* @param entity the entity
* @param clazz the clazz
* @param fieldMap the field map
* @param nameHandler the name handler
* @return bound sql
*/
public static BoundSql buildListSql(Object entity, Class<?> clazz,
Map<String, Object> fieldMap, NameHandler nameHandler) {
BoundSql boundSql = SqlUtils.buildQuerySql(entity, clazz, fieldMap, nameHandler);
String orderBy = (String) fieldMap.get(ORDER_BY);
if (StringUtils.isNotBlank(orderBy)) {
boundSql.setSql(boundSql.getSql() + " ORDER BY " + orderBy);
} else {
boundSql.setSql(boundSql.getSql() + " ORDER BY " + boundSql.getPrimaryKey() + " DESC");
}
return boundSql;
}
查询的sql跟单个查询等是一样的,列表查询只不过最后多了一个排序处理,如果没有指定排序,则按主键倒序排序。
SqlUtils.buildQuerySql方法:
/**
* 按设置的条件构建查询sql
*
* @param entity the entity
* @param clazz the clazz
* @param fieldMap the field map
* @param nameHandler the name handler
* @return bound sql
*/
public static BoundSql buildQuerySql(Object entity, Class<?> clazz,
Map<String, Object> fieldMap, NameHandler nameHandler) {
if (clazz == null) {
clazz = entity.getClass();
}
List<AutoField> autoFields = (List<AutoField>) fieldMap.get(AUTO_FIELDS);
List<String> includeFields = (List<String>) fieldMap.get(INCLUDE_FIELDS);
List<String> excludeFields = (List<String>) fieldMap.get(EXCLUDE_FIELDS);
String tableName = nameHandler.getTableName(clazz.getSimpleName());
String primaryName = nameHandler.getPrimaryName(clazz.getSimpleName());
List<AutoField> allAutoField = getAllAutoField(entity, autoFields, QUERY);
String columns = SqlUtils.buildColumnSql(clazz, nameHandler, includeFields, excludeFields);
StringBuilder querySql = new StringBuilder("SELECT " + columns + " FROM ");
querySql.append(tableName);
List<Object> params = Collections.EMPTY_LIST;
if (!CollectionUtils.isEmpty(allAutoField)) {
querySql.append(" WHERE ");
BoundSql boundSql = SqlUtils.builderWhereSql(allAutoField, nameHandler);
params = boundSql.getParams();
querySql.append(boundSql.getSql());
}
return new BoundSql(querySql.toString(), primaryName, params);
}
这里,先把操作字段,实体类的属性等合并成一个总的AutoField列表,然后再根据这个列表进行sql的拼装。
首先拼装处理拥有黑白名单的列:
/**
* 构建查询的列sql
*
* @param clazz
* @param nameHandler
* @param includeField
* @param excludeField
* @return
*/
public static String buildColumnSql(Class<?> clazz, NameHandler nameHandler,
List<String> includeField, List<String> excludeField) {
StringBuilder columns = new StringBuilder();
//有白名单,直接按白名单走
if (!CollectionUtils.isEmpty(includeField)) {
for (String fieldName : includeField) {
String columnName = nameHandler.getColumnName(fieldName);
columns.append(columnName);
columns.append(",");
}
} else {
//获取属性信息
BeanInfo beanInfo = ClassUtils.getSelfBeanInfo(clazz);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
String fieldName = pd.getName();
//忽略黑名单field
if (!CollectionUtils.isEmpty(excludeField) && excludeField.contains(fieldName)) {
continue;
}
String columnName = nameHandler.getColumnName(fieldName);
columns.append(columnName);
columns.append(",");
}
}
columns.deleteCharAt(columns.length() - 1);
return columns.toString();
}
然后是拼装where条件,拼装时需要注意处理多个值、in、not in、null、or等情况:
/**
* 构建where条件sql
*
* @param autoFields the auto fields
* @param nameHandler the name handler
* @return bound sql
*/
private static BoundSql builderWhereSql(List<AutoField> autoFields, NameHandler nameHandler) {
StringBuilder sql = new StringBuilder();
List<Object> params = new ArrayList<Object>();
for (int i = 0; i < autoFields.size(); i++) {
AutoField autoField = autoFields.get(i);
if (AutoField.WHERE_FILED != autoField.getType()) {
continue;
}
if (sql.length() > 0) {
sql.append(" ").append(autoField.getSqlOperator()).append(" ");
}
String columnName = nameHandler.getColumnName(autoField.getName());
Object[] values = autoField.getValues();
if (StringUtils.equalsIgnoreCase(IN, autoField.getFieldOperator())
|| StringUtils.equalsIgnoreCase(NOT_IN, autoField.getFieldOperator())) {
//in,not in的情况
sql.append(columnName).append(" ").append(autoField.getFieldOperator()).append(" ");
sql.append("(");
for (int j = 0; j < values.length; j++) {
sql.append(" ?");
params.add(values[j]);
if (j != values.length - 1) {
sql.append(",");
}
}
sql.append(")");
} else if (values == null) {
//null 值
sql.append(columnName).append(" ").append(autoField.getFieldOperator())
.append(" NULL");
} else if (values.length == 1) {
//一个值 =
sql.append(columnName).append(" ").append(autoField.getFieldOperator())
.append(" ?");
params.add(values[0]);
} else {
//多个值,or的情况
sql.append("(");
for (int j = 0; j < values.length; j++) {
sql.append(columnName).append(" ").append(autoField.getFieldOperator())
.append(" ?");
params.add(values[j]);
if (j != values.length - 1) {
sql.append(" OR ");
}
}
sql.append(")");
}
}
return new BoundSql(sql.toString(), null, params);
}
参数fieldMap就是保存在ThreadLocal中的一些操作信息,代码:
/**
* 获取操作的属性信息,并回收资源
*
* @return
*/
protected Map<String, Object> getFieldMap() {
List<AutoField> autoFields = AUTO_FIELDS.get();
List<String> includeFields = INCLUDE_FIELDS.get();
List<String> excludeFields = EXCLUDE_FIELDS.get();
String orderBy = LOCAL_ORDER_BY.get();
Map<String, Object> fieldMap = new HashMap<String, Object>();
fieldMap.put(SqlUtils.AUTO_FIELDS, autoFields);
fieldMap.put(SqlUtils.INCLUDE_FIELDS, includeFields);
fieldMap.put(SqlUtils.EXCLUDE_FIELDS, excludeFields);
fieldMap.put(SqlUtils.ORDER_BY, orderBy);
//资源回收
LOCAL_ORDER_BY.remove();
INCLUDE_FIELDS.remove();
EXCLUDE_FIELDS.remove();
AUTO_FIELDS.remove();
return fieldMap;
}
在获取之即将它们移除,这是一个好的习惯。
nameHandler如果为空就实例化一个默认的:
/**
* 获取名称处理器
*
* @return
*/
protected NameHandler getNameHandler() {
if (this.nameHandler == null) {
this.nameHandler = new DefaultNameHandler();
}
return this.nameHandler;
}
楼主请问可以共享下完整的源码,让我们学习下吗?谢谢