本文对apache dbutils项目的源码进行分析,目录如下:
1、基本使用
2、DBUtilsy源码分析
2.1 QueryRunner
2.2 AbstractQueryRunner
2.3 结果集处理
3、总结
1、基本使用
从上述使用示例中可以看出DBUtils的使用时序如下:
上图中的第4步有误,就是返回而已
可以看出dbutils核心的就两部分:
- 提供的多样的API及对SQL参数的封装处理
- 结果集转换到JavaBean的处理
接下来对源码进行分析
2、DBUtils源码分析
对DBUtils代码的分析按上述执行过程进行
2.1、QueryRunner
QueryRunner继承于AbstractQueryRunner类,提供了多样的增删改查接口,大多接口都是调用到最核心的几个方法上,本文仅取两个典型的方法:
- <T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params)
- <T> T insertBatch(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object[][] params)
来做分析,其它的大致相同,可参考源码。
另外,对AbstractQueryRunner的分析,在下面专门进行。
/**
* Calls query after checking the parameters to ensure nothing is null.
* @param conn The connection to use for the query call.
* @param closeConn True if the connection should be closed, false otherwise.
* @param sql The SQL statement to execute.
* @param params An array of query replacement parameters. Each row in
* this array is one set of batch replacement values.
* @return The results of the query.
* @throws SQLException If there are database or parameter errors.
*/
private <T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params)
throws SQLException {
if (conn == null) {
throw new SQLException("Null connection");
}
if (sql == null) {
if (closeConn) {
close(conn);
}
throw new SQLException("Null SQL statement");
}
if (rsh == null) {
if (closeConn) {
close(conn);
}
throw new SQLException("Null ResultSetHandler");
}
PreparedStatement stmt = null;
ResultSet rs = null;
T result = null;
try {
// 对PreparedStatement对象进行初始化
stmt = this.prepareStatement(conn, sql);
// 将参数填充到语句中,该函数是在AbstractQueryRunner内实现的,下面做分析
this.fillStatement(stmt, params);
// 对结果集进行包装
rs = this.wrap(stmt.executeQuery());
// 对结果集进行转换处理
result = rsh.handle(rs);
} catch (SQLException e) {
this.rethrow(e, sql, params);
} finally {
try {
close(rs);
} finally {
close(stmt);
if (closeConn) {
close(conn);
}
}
}
return result;
}
批量插入操作
/**
* Executes the given batch of INSERT SQL statements.
* @param conn The connection to use for the query call.
* @param closeConn True if the connection should be closed, false otherwise.
* @param sql The SQL statement to execute.
* @param rsh The handler used to create the result object from
* the <code>ResultSet</code> of auto-generated keys.
* @param params The query replacement parameters.
* @return The result generated by the handler.
* @throws SQLException If there are database or parameter errors.
* @since 1.6
*/
private <T> T insertBatch(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object[][] params)
throws SQLException {
if (conn == null) {
throw new SQLException("Null connection");
}
if (sql == null) {
if (closeConn) {
close(conn);
}
throw new SQLException("Null SQL statement");
}
if (params == null) {
if (closeConn) {
close(conn);
}
throw new SQLException("Null parameters. If parameters aren't need, pass an empty array.");
}
PreparedStatement stmt = null;
T generatedKeys = null;
try {
// Statement.RETURN_GENERATED_KEYS 表示获取插入SQL语句的ID值
// 设定为自增长id方式
stmt = this.prepareStatement(conn, sql, Statement.RETURN_GENERATED_KEYS);
// 遍历参数列表,逐条参数信息填充
for (int i = 0; i < params.length; i++) {
// 将参数填充到语句中,该函数是在AbstractQueryRunner内实现的,下面做分析
this.fillStatement(stmt, params[i]);
stmt.addBatch();
}
// 批量执行
stmt.executeBatch();
// 获取自增长ID
ResultSet rs = stmt.getGeneratedKeys();
generatedKeys = rsh.handle(rs);
} catch (SQLException e) {
this.rethrow(e, sql, (Object[])params);
} finally {
close(stmt);
if (closeConn) {
close(conn);
}
}
return generatedKeys;
}
Apache-dbutils还提供了线程安全的SQL执行类 AsyncQueryRunner,其内部最终也是通过QueryRunner来实现,此处就不做分析了。
2.2 AbstractQueryRunner
AbstractQueryRunner是一个抽象类,是 QueryRunner和 AsyncQueryRunner 的基类,主要提供了两方面的东西
- 对SQL语句参数进行填充
- 关闭数据库连接,SQL 参数的准备等
对参数填充的方法fillStatement(stmt,params)
/**
* Fill the <code>PreparedStatement</code> replacement parameters with the
* given objects.
*
* @param stmt
* PreparedStatement to fill
* @param params
* Query replacement parameters; <code>null</code> is a valid
* value to pass in.
* null值是合法参数
* @throws SQLException
* if a database access error occurs
*/
/**
* fillStatement方法的处理流程:
* fillStatement的主要作用就是将需要在SQL中添加的参数进行填充到准备语句中去。
* 在填充过程中处理:
* 1、通过参数个数做校验
* 通过获取准备语句的参数元信息与参数值个数做比对,不合法则抛出SQL异常;
* 2、调用准备语句方法(setObject(...))逐个参数值按序设定
* 3、对参数值为null的处理
* 参数值为null时,主要正确设置null对应参数的SQL类型。
* 通过参数元信息中获取对应位置的参数类型信息,将此信息设置;否则就使用默认的vchar类型设定
*/
public void fillStatement(PreparedStatement stmt, Object... params)
throws SQLException {
// check the parameter count, if we can
/* ParameterMetaData类用来表示PreparedStatement实例的参数相关信息,
* 包括参数个数,类型等一些属性。这些属性信息通过
* ParameterMetaData getParameterMetaData() throws SQLException;
* 方法获取。ParameterMetaData的使用参考官方API文档即可。
*/
ParameterMetaData pmd = null;
/** pmdKnownBroken是本类的一个booleanl类型的成员变量,用来区分一个JDBC驱动是否支持
* {@link ParameterMetaData#getParameterType(int) };的操作。
* 设置为true,则表示不支持;false 则表示支持
*/
if (!pmdKnownBroken) {
// 获取参数相关的元数据信息,包含参数个数和类型等信息
pmd = stmt.getParameterMetaData();
int stmtCount = pmd.getParameterCount();
int paramsCount = params == null ? 0 : params.length;
if (stmtCount != paramsCount) {
throw new SQLException("Wrong number of parameters: expected "
+ stmtCount + ", was given " + paramsCount);
}
}
// nothing to do here
if (params == null) {
return;
}
for (int i = 0; i < params.length; i++) {
if (params[i] != null) {
stmt.setObject(i + 1, params[i]);
} else {
// VARCHAR works with many drivers regardless
// of the actual column type. Oddly, NULL and
// OTHER don't work with Oracle's drivers.
int sqlType = Types.VARCHAR;
if (!pmdKnownBroken) {
try {
/*
* It's not possible for pmdKnownBroken to change from
* true to false, (once true, always true) so pmd cannot
* be null here.
*/
sqlType = pmd.getParameterType(i + 1);
} catch (SQLException e) {
pmdKnownBroken = true;
}
}
// 对null参数值的处理
stmt.setNull(i + 1, sqlType);
}
}
}
DBUtils还提供了一种根据java实体和指定属性名的方式来进行参数填充
/**
* Fill the <code>PreparedStatement</code> replacement parameters with the
* given object's bean property values.
* 通过bean以及要执行的属性名称来进行参数填充
*
* @param stmt
* PreparedStatement to fill
* @param bean
* A JavaBean object
* @param propertyNames
* An ordered array of property names (these should match the
* getters/setters); this gives the order to insert values in the
* statement
* @throws SQLException
* If a database access error occurs
*/
public void fillStatementWithBean(PreparedStatement stmt, Object bean,
String... propertyNames) throws SQLException {
PropertyDescriptor[] descriptors;
try {
// 获取实体的属性集合
descriptors = Introspector.getBeanInfo(bean.getClass())
.getPropertyDescriptors();
} catch (IntrospectionException e) {
throw new RuntimeException("Couldn't introspect bean "
+ bean.getClass().toString(), e);
}
PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
// 遍历传入的属性列表,匹配对应的实体属性,存储到sorted内
for (int i = 0; i < propertyNames.length; i++) {
String propertyName = propertyNames[i];
if (propertyName == null) {
throw new NullPointerException("propertyName can't be null: "
+ i);
}
boolean found = false;
// 依次遍历实体的属性集合
for (int j = 0; j < descriptors.length; j++) {
PropertyDescriptor descriptor = descriptors[j];
// 此处直接进行比较
if (propertyName.equals(descriptor.getName())) {
/**
* 此处直接将元素属性名复制,作为表字段名称;如果要进行实体属性名(驼峰命名)
* 与数据库表字段名映射,可在下面代码中处理即可。
*/
sorted[i] = descriptor;
found = true;
break;
}
}
if (!found) {
throw new RuntimeException("Couldn't find bean property: "
+ bean.getClass() + " " + propertyName);
}
}
// 到此,只是将实体属性与给定的进行过滤提取了一遍,而真正的值还在实体bean内部
fillStatementWithBean(stmt, bean, sorted);
}
上面调用了fillStatementWithBean(PreparedStatement stmt, Object bean,
PropertyDescriptor[] properties)来获取属性值并进行填充
/**
* Fill the <code>PreparedStatement</code> replacement parameters with the
* given object's bean property values.
*
* @param stmt
* PreparedStatement to fill
* @param bean
* a JavaBean object
* @param properties
* an ordered array of properties; this gives the order to insert
* values in the statement
* @throws SQLException
* if a database access error occurs
*
* 该方法将根据传入的实体属性集合通过反射bean获取属性值来进行参数填充
*/
public void fillStatementWithBean(PreparedStatement stmt, Object bean,
PropertyDescriptor[] properties) throws SQLException {
Object[] params = new Object[properties.length];
// 遍历元素属性名集合
for (int i = 0; i < properties.length; i++) {
PropertyDescriptor property = properties[i];
Object value = null;
// 获取属性的读方法
Method method = property.getReadMethod();
if (method == null) {
throw new RuntimeException("No read method for bean property "
+ bean.getClass() + " " + property.getName());
}
try {
// 通过回调属性读方法获取到属性值
value = method.invoke(bean, new Object[0]);
} catch (InvocationTargetException e) {
throw new RuntimeException("Couldn't invoke method: " + method,
e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(
"Couldn't invoke method with 0 arguments: " + method, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Couldn't invoke method: " + method,
e);
}
// 收集属性值
params[i] = value;
}
// 参数填充
fillStatement(stmt, params);
}
AbstractQueryRunner类还提供了一个抛出SQL、参数信息的异常方法:
/**
* Throws a new exception with a more informative error message.
*
* @param cause
* The original exception that will be chained to the new
* exception when it's rethrown.
*
* @param sql
* The query that was executing when the exception happened.
*
* @param params
* The query replacement parameters; <code>null</code> is a valid
* value to pass in.
*
* @throws SQLException
* if a database access error occurs
*/
protected void rethrow(SQLException cause, String sql, Object... params)
throws SQLException {
// 获取异常信息
String causeMessage = cause.getMessage();
if (causeMessage == null) {
causeMessage = "";
}
StringBuffer msg = new StringBuffer(causeMessage);
// 添加出现异常的SQL和参数信息
msg.append(" Query: ");
msg.append(sql);
msg.append(" Parameters: ");
if (params == null) {
msg.append("[]");
} else {
msg.append(Arrays.deepToString(params));
}
// 构建并抛出异常
SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
cause.getErrorCode());
e.setNextException(cause);
throw e;
}
2.3 结果集处理
ResultSetHandler接口,将ResultSet转换为一个Object对象
/**
* Implementations of this interface convert ResultSets into other objects.
*
* @param <T> the target type the input ResultSet will be converted to.
*/
public interface ResultSetHandler<T> {
/**
* Turn the <code>ResultSet</code> into an Object.
* 将ResultSet转换为一个Object对象
* @param rs The <code>ResultSet</code> to handle. It has not been touched
* before being passed to this method.
*
* @return An Object initialized with <code>ResultSet</code> data. It is
* legal for implementations to return <code>null</code> if the
* <code>ResultSet</code> contained 0 rows.
*
* @throws SQLException if a database access error occurs
*/
T handle(ResultSet rs) throws SQLException;
}
其实现有:
- BeanHandler:将结果集中的第一行数据转换成一个JavaBean实例
- BeanListHandler:将结果集中的每一行数据都转成一个JavaBean实例,存放到List中
- BeanMapHandler:将结果集中的每一行数据都转成一个JavaBean实例,并指定某一列作为Key,存放到Map中
- AbstractKeyedHandler:将每一行记录转换成一个键值对
- AbstractListHandler:将结果集转换到一个List列表内
- ArrayHandler:结果集中的第一行数据转换成Object数组
- ArrayListHandler:把结果集中的每一行数据都转换成一个Object[]数组,再存放在List中
- ColumnListHandler:将结果集中某一列的数据存放到List中
等等,提供了各种各样的实现。
此处,仅对BeanHandler类的实现做分析,其它大多雷同,就不分析了。
/**
* <code>ResultSetHandler</code> implementation that converts the first
* <code>ResultSet</code> row into a JavaBean. This class is thread safe.
* 将结果集中的第一行数据转换成一个JavaBean实例。
*
* @param <T> the target bean type
* @see org.apache.commons.dbutils.ResultSetHandler
*/
public class BeanHandler<T> implements ResultSetHandler<T> {
/**
* The Class of beans produced by this handler.
*/
private final Class<T> type;
/**
* The RowProcessor implementation to use when converting rows
* into beans.
* 真正的转换器,基本所有的结果集的处理都在这个类里面,接下来做重点分析
*/
private final RowProcessor convert;
/**
* 有两个构造方法省略......
*/
/**
* Convert the first row of the <code>ResultSet</code> into a bean with the
* <code>Class</code> given in the constructor.
* @param rs <code>ResultSet</code> to process.
* @return An initialized JavaBean or <code>null</code> if there were no
* rows in the <code>ResultSet</code>.
*
* @throws SQLException if a database access error occurs
* @see org.apache.commons.dbutils.ResultSetHandler#handle(java.sql.ResultSet)
*/
@Override
public T handle(ResultSet rs) throws SQLException {
// 通过转换器来实现
return rs.next() ? this.convert.toBean(rs, this.type) : null;
}
}
很容易看出dbutils提供了一个对RowProcessor的基本实现:BasicRowProcessor
我们来看上面的转换器中的实现:
public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
return this.convert.toBean(rs, type);
}
在BasicRowProcessor内又通过调用BeanProcessor的方法来实现,专门处理对JavaBean的转换
根据结果集和类类型来创建实体类,经历以下处理过程:
- 获取bean的所有类型
- 获取结果集中的类型和值
- 然后将结果集中的类型与Bean中的类型匹配(涉及到表字段的命名与实体属性名转换问题)
- 通过实体bean的setter方法反射设定
public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
// 获得JavaBean的属性
PropertyDescriptor[] props = this.propertyDescriptors(type);
// 获得结果集中的字段属性
// ResultSetMetaData为结果集元数据类,使用可参考官方API
ResultSetMetaData rsmd = rs.getMetaData();
// columnToProperty[3]=4 就是说ResultSetMetaData里的第三个字段
// 对应于bean的PropertyDescriptor里面的第四个属性
// 数组中的数字是拿 结果集中的字段属性去按个匹配Bean中的属性,得到合法属性在结果集中的索引序列,
// 后续转换bean时都以此为准,实际上就是将表中存在的字段在bean中不存在的过滤出去了,bean中不存在的
// 是无法属性设定的
int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
// 创建实体对象
return this.createBean(rs, type, props, columnToProperty);
}
// 将结果集中存在的属性在实体bean 中进行过滤,并返回存在的索引
// 为后续转换bean时提供依据,实际上就是将表中存在的字段在bean中不存在的过滤出去了,bean中不存在的
// 是无法属性设定的
protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
PropertyDescriptor[] props) throws SQLException {
int cols = rsmd.getColumnCount();
// 定义并初始化数组为-1
int[] columnToProperty = new int[cols + 1];
Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
// 逐个遍历结果集属性
for (int col = 1; col <= cols; col++) {
// 获得字段名称
String columnName = rsmd.getColumnLabel(col);
if (null == columnName || 0 == columnName.length()) {
columnName = rsmd.getColumnName(col);
}
String propertyName = columnToPropertyOverrides.get(columnName);
if (propertyName == null) {
propertyName = columnName;
}
// 遍历Bean属性
for (int i = 0; i < props.length; i++) {
// 忽略大小写比较
if (propertyName.equalsIgnoreCase(props[i].getName())) {
columnToProperty[col] = i;
break;
}
}
}
return columnToProperty;
}
已获得足够多的信息,开始创建实体Bean
/**
* Creates a new object and initializes its fields from the ResultSet.
* 创建实体类,并根据结果集初始化其属性值
* @param <T> The type of bean to create
* @param rs The result set.
* @param type The bean type (the return type of the object).
* @param props The property descriptors.
* @param columnToProperty The column indices in the result set.
* @return An initialized object.
* @throws SQLException if a database error occurs.
*/
private <T> T createBean(ResultSet rs, Class<T> type,
PropertyDescriptor[] props, int[] columnToProperty)
throws SQLException {
// 创建对象
T bean = this.newInstance(type);
// 按提取好的合法的属性集逐个设定
for (int i = 1; i < columnToProperty.length; i++) {
if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
continue;
}
// 提取Bean属性
PropertyDescriptor prop = props[columnToProperty[i]];
Class<?> propType = prop.getPropertyType();
Object value = null;
if(propType != null) {
// 从结果集中根据属性获得值
value = this.processColumn(rs, i, propType);
// 原生类型的值为空,则设定默认值
if (value == null && propType.isPrimitive()) {
// primitiveDefaults中通过静态块代码已将原生类型的默认值初始好了
value = primitiveDefaults.get(propType);
}
}
// 设定属性值
this.callSetter(bean, prop, value);
}
return bean;
}
从结果集中根据属性获得值
protected Object processColumn(ResultSet rs, int index, Class<?> propType)
throws SQLException {
// index为结果集中的索引, proType为该索引对应的数据转换为的目的JavaBean内的属性
// 根据索引和类型,将提取到的结果值做转换并返回
if ( !propType.isPrimitive() && rs.getObject(index) == null ) {
return null;
}
if (propType.equals(String.class)) {
return rs.getString(index);
} else if (
propType.equals(Integer.TYPE) || propType.equals(Integer.class)) {
return Integer.valueOf(rs.getInt(index));
} else if (
propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) {
return Boolean.valueOf(rs.getBoolean(index));
} else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) {
return Long.valueOf(rs.getLong(index));
} else if (
propType.equals(Double.TYPE) || propType.equals(Double.class)) {
return Double.valueOf(rs.getDouble(index));
} else if (
propType.equals(Float.TYPE) || propType.equals(Float.class)) {
return Float.valueOf(rs.getFloat(index));
} else if (
propType.equals(Short.TYPE) || propType.equals(Short.class)) {
return Short.valueOf(rs.getShort(index));
} else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) {
return Byte.valueOf(rs.getByte(index));
} else if (propType.equals(Timestamp.class)) {
return rs.getTimestamp(index);
} else if (propType.equals(SQLXML.class)) {
return rs.getSQLXML(index);
} else {
return rs.getObject(index);
}
}
设置属性值
// 调用反射调用JavaBean的setter方法,来注入结果集中的值
private void callSetter(Object target, PropertyDescriptor prop, Object value)
throws SQLException {
// 获取setter方法
Method setter = prop.getWriteMethod();
if (setter == null) {
return;
}
Class<?>[] params = setter.getParameterTypes();
try {
// convert types for some popular ones
// 对几种特殊类型做的转换处理
if (value instanceof java.util.Date) {
final String targetType = params[0].getName();
if ("java.sql.Date".equals(targetType)) {
value = new java.sql.Date(((java.util.Date) value).getTime());
} else
if ("java.sql.Time".equals(targetType)) {
value = new java.sql.Time(((java.util.Date) value).getTime());
} else
if ("java.sql.Timestamp".equals(targetType)) {
Timestamp tsValue = (Timestamp) value;
int nanos = tsValue.getNanos();
value = new java.sql.Timestamp(tsValue.getTime());
((Timestamp) value).setNanos(nanos);
}
} else
if (value instanceof String && params[0].isEnum()) {
value = Enum.valueOf(params[0].asSubclass(Enum.class), (String) value);
}
// Don't call setter if the value object isn't the right type
// 对参数类型和值类型做匹配检查
if (this.isCompatibleType(value, params[0])) {
// 调用set方法 设定值
setter.invoke(target, new Object[]{value});
} else {
throw new SQLException(
"Cannot set " + prop.getName() + ": incompatible types, cannot convert "
+ value.getClass().getName() + " to " + params[0].getName());
// value cannot be null here because isCompatibleType allows null
}
} catch (IllegalArgumentException e) {
throw new SQLException(
"Cannot set " + prop.getName() + ": " + e.getMessage());
} catch (IllegalAccessException e) {
throw new SQLException(
"Cannot set " + prop.getName() + ": " + e.getMessage());
} catch (InvocationTargetException e) {
throw new SQLException(
"Cannot set " + prop.getName() + ": " + e.getMessage());
}
}
对参数的类型和值做匹配检查
// 对参数的类型和值做匹配检查
private boolean isCompatibleType(Object value, Class<?> type) {
// Do object check first, then primitives
if (value == null || type.isInstance(value)) {
return true;
} else if (type.equals(Integer.TYPE) && value instanceof Integer) {
return true;
} else if (type.equals(Long.TYPE) && value instanceof Long) {
return true;
} else if (type.equals(Double.TYPE) && value instanceof Double) {
return true;
} else if (type.equals(Float.TYPE) && value instanceof Float) {
return true;
} else if (type.equals(Short.TYPE) && value instanceof Short) {
return true;
} else if (type.equals(Byte.TYPE) && value instanceof Byte) {
return true;
} else if (type.equals(Character.TYPE) && value instanceof Character) {
return true;
} else if (type.equals(Boolean.TYPE) && value instanceof Boolean) {
return true;
}
return false;
}
到此,整个dbutils的大多数的源码已经剖析完了,下面对此做个总结。
3、总结
dbutils对封装了jdbc的操作,简化了jdbc的操作,小巧简单实用。根据适合的应用场景使用就好。
对于JDBC数据库编程,一般有三方面的问题:
1、数据库连接的管理
数据库的连接作为宝贵的IO资源,通过池化的方式,牺牲空间换取时间,提高效率。
2、数据库表与JavaBean之间的映射
就是数据库中的表字段与JavaBean中的成员变量配对映射的问题。
这个问题可通过以下3种思路解决:
-
a. sql语句
jdbc中执行SQL时写明表字段名以及对应的参数即可。
如,最简单的对orm框架apache dbutils中的处理,这样简单粗暴,提供了更强的灵活性给用户,对于一些复杂SQL,涉及多表操作,或统计信息之类的,很容易灵活自定义,但缺点就是开发工作量繁复,也不适用于扩展维护。 -
b. 在JavaBean中明确指定对应表和对应的字段
如Hibernate的做法。这样做的好处就是省事,不必每条数据库操作都去写SQL语句了。SQL语句已有Hibernae根据映射关系给你动态生成SQL去执行。
-
c. 动态映射
只指定一个表名称即可,剩下的工具(如jfinal)帮你搞定,这么做实际上你的JavaBean的字段都不用自己写。
原理就在于,通过表名称,可获取到表结构及表字段,剩下的就是填充构造SQL了。那么,问题来了,属性与字段如何对应? 用时指定呗,用的时候指定即可。
这同时就暴露了一个问题,表字段属性与javaBean强相关了。如果表结构有变动,就需要将所有有影响的地方修改。但好处是开发很迅速啊。
3、数据库操作的监控
通常,随着系统运行,数据量提升后,会对数据库方面开始优化。优化的前提需要依据,依据对数据库操作进行的监控记录。
如, 提取热点数据做缓存;执行缓慢SQL做优化;冗余数据优化等等。
以上,仅是个人的学习总结分享,如有不到位甚至错误等方面,真诚欢迎各位赐教讨论,互相学习,谢谢!