上一篇深入Java日记——自己写一个ORM框架(1)中提到了如何将数据库的表生成对象的JavaBean,这篇博客就讲一下如何利用JavaBean增删改查,主要行为是拼接sql语句
1.增
首先要写一个最基础的执行sql语句的方法
我们使用的PreparedStatement来执行,添加参数可以通过一个工具类来实现
public class JDBCUtils {
public static void handleParams(PreparedStatement ps,Object[]params){
if (params!=null){
for (int i=0;i<params.length;i++){
try {
//参数是从1开始
ps.setObject(i+1,params[i]);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
执行sql语句方法的主体
public int executeDML(String sql,Object[] params){
Connection connection=DBManager.getConn();
int count=0;
PreparedStatement ps=null;
try {
ps=connection.prepareStatement(sql);
JDBCUtils.handleParams(ps,params);
count=ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBManager.close(ps,connection);
}
return count;
}
数据库的名字和字段的名字都是采用下划线分割法,但字段名是采用小驼峰,所以要进行一次转换
public static String camel2Underline(String line){
StringBuilder result = new StringBuilder();
if (line != null && line.length() > 0) {
// 将第一个字符处理成小写
result.append(line.substring(0, 1).toLowerCase());
// 循环处理其余字符
for (int i = 1; i < line.length(); i++) {
String s = line.substring(i, i + 1);
// 在大写字母前添加下划线
if (s.equals(s.toUpperCase()) && !Character.isDigit(s.charAt(0))) {
result.append("_");
}
// 其他字符直接转成大写
result.append(s.toLowerCase());
}
}
return result.toString();
}
插入时获取属性值主要通过反射来进行
public static Object getFieldValue(String field,Object obj){
Class clazz=obj.getClass();
Field idField= null;
Object id=null;
try {
idField = clazz.getDeclaredField(field);
idField.setAccessible(true);
id=idField.get(obj);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return id;
}
插入方法的主要步骤是:拼接前置sql语句,转换和拼接属性名,拼接后置sql语句
/**
* 插入对象到数据库,拼接的sql如 insert into users(id,name) values(?,?)
* @param obj 要储存的数据
*/
public void insert(Object obj){
Class clazz = obj.getClass();
//通过Class获取TableInfo
TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
//拼接前置sql
StringBuilder stringBuilder=new StringBuilder("insert into " + tableInfo.getTname() + "(");
Field[]fields=clazz.getDeclaredFields();
Object[] fieldValue=new Object[fields.length];
//遍历所有属性,获取他们在要插入对象的值
for (int i=0;i<fields.length;i++){
Field field=fields[i];
fieldValue[i]=ReflectUtils.getFieldValue(field.getName(),obj);
stringBuilder.append(StringUtils.camel2Underline(field.getName())+",");
}
//删除最后一个逗号
stringBuilder.delete(stringBuilder.length()-1,stringBuilder.length());
//拼接后置sql
stringBuilder.append(")");
stringBuilder.append(" values(");
for (int i=0;i<fields.length;i++){
stringBuilder.append("?,");
}
stringBuilder.delete(stringBuilder.length()-1,stringBuilder.length());
stringBuilder.append(")");
String sql = stringBuilder.toString();
System.out.println(sql);
executeDML(sql, fieldValue);
}
2.删
删除我们采用两种方式,第一种是通过类和主键,另一种是通过对象实例删除
通过类和主键删除
public void delete(Class clazz,Object id){
//通过Class获取TableInfo
TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
//获得主键
ColumnInfo columnInfo = tableInfo.getOnlyPriKey();
String sql = "delete from " + tableInfo.getTname() + " where " + columnInfo.getName() + " =?";
executeDML(sql, new Object[]{id});
}
通过对象实例删除
public void delete(Object obj){
Class clazz = obj.getClass();
//通过Class获取TableInfo
TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
//获得主键
ColumnInfo columnInfo = tableInfo.getOnlyPriKey();
String sql = "delete from " + tableInfo.getTname() + " where " + columnInfo.getName() + " =?";
//通过反射获取主键
Object id = ReflectUtils.getFieldValue(StringUtils.underlineToSmallCamel(columnInfo.getName()), obj);
executeDML(sql, new Object[]{id});
}
3.改
改的sql拼接方式有点类似插入
/**
* 只更新指定字段 拼接的sql如 update users set name = ? where id = ?
* @param obj 所要更新的对象
* @param fieldNames 要更新的字段名
* @return 执行sql语句后影响的行数
*/
public int update(Object obj,String[] fieldNames){
Class clazz = obj.getClass();
//通过Class获取TableInfo
TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
//获得主键
ColumnInfo columnInfo = tableInfo.getOnlyPriKey();
List<Object> params=new ArrayList<>();
StringBuilder stringBuilder=new StringBuilder();
//拼接前置sql语句
stringBuilder.append("update " + tableInfo.getTname()+" set ");
//遍历要修改的属性,获取他们在要插入对象的值
for (String field:fieldNames){
stringBuilder.append(StringUtils.camel2Underline(field)+" = ?");
params.add(ReflectUtils.getFieldValue(field,obj));
stringBuilder.append(",");
}
//删除最后一个逗号
stringBuilder.delete(stringBuilder.length()-1,stringBuilder.length());
//拼接后置sql
stringBuilder.append(" where "+tableInfo.getOnlyPriKey().getName()+"=?");
String sql = stringBuilder.toString();
//通过反射获取主键
Object id = ReflectUtils.getFieldValue(StringUtils.underlineToSmallCamel(columnInfo.getName()), obj);
if (id==null){
return 0;
}
System.out.println(sql);
params.add(id);
return executeDML(sql, params.toArray());
}
4.查
查的话有三种查询,第一种是查询多行记录,第二种是查询某个记录,第三种是查询某个值
查询的时候要将记录转换为对应JavaBean的List,需要用到JavaBean的set方法,我们可以通过反射来进行
public static void invokeSet(String field,Object value,Object obj){
Class clazz=obj.getClass();
try {
Method method=clazz.getDeclaredMethod("set"+StringUtils.underlineToBigCamel(field),value.getClass());
method.invoke(obj,value);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
查询多行记录
/**
* 返回多行记录,并将每行记录封装到指定的类中
* @param sql 查询语句
* @param clazz 封装数据的javabean类的Class对象
* @param params sql的参数
* @return 查询到的结果
*/
public List queryRow(final String sql,final Class clazz,final Object[] params){
Connection connection=DBManager.getConn();
ResultSet resultSet=null;
try {
PreparedStatement ps=connection.prepareStatement(sql);
JDBCUtils.handleParams(ps,params);
resultSet=ps.executeQuery();
List result=new ArrayList();
ResultSetMetaData resultSetMetaData= null;
try {
resultSetMetaData = resultSet.getMetaData();
while (resultSet.next()){
Object rowObj=clazz.newInstance();
//获得遍历该行的字段,并将其赋值给对象
for(int i=0;i<resultSetMetaData.getColumnCount();i++){
//获取字段名
String columnName=resultSetMetaData.getColumnLabel(i+1);
//获取字段属性
Object columnValue=resultSet.getObject(i+1);
//赋值
ReflectUtils.invokeSet(columnName,columnValue,rowObj);
}
result.add(rowObj);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}finally {
DBManager.close(preparedStatement,connection);
}
return result;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
查询某个记录
/**
* 返回一行记录,并将一行记录封装到指定的类中
* @param sql 查询语句
* @param clazz 封装数据的javabean类的Class对象
* @param params sql的参数
* @return 查询到的结果
*/
public Object queryUniqueRow(String sql,Class clazz,Object[] params){
List result=queryRow(sql,clazz,params);
if (result!=null&&result.size()>0){
return result.get(0);
}
return null;
}
查询某个值
/**
* 返回一个值(一行一列)
* @param sql 查询语句
* @param params sql的参数
* @return 查询到的结果
*/
public Object queryValue(String sql,Object[] params){
Connection connection=DBManager.getConn();
ResultSet resultSet=null;
try {
PreparedStatement ps=connection.prepareStatement(sql);
JDBCUtils.handleParams(ps,params);
resultSet=ps.executeQuery();
try {
while (resultSet.next()){
//只返回第一条记录的第一个字段的值
return resultSet.getObject(1);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBManager.close(preparedStatement,connection);
}
return null;
}
}
以上是核心代码,之后我们还可以自定义一个连接池和自定义Query对象的工厂来适配不同数据库,这部分的代码留给你们去实现