一、ORM框架的发展历程
1. JDBC操作
1.1 JDBC操作的特点
最初的时候我们肯定是直接通过jdbc来直接操作数据库的,本地数据库我们有一张t_user表,那么我们的操作流程是 复制代码
// 注册 JDBC 驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 打开连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8&serverTimezone=UTC", "root", "123456"); // 执行查询 stmt = conn.createStatement(); String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1"; ResultSet rs = stmt.executeQuery(sql); // 获取结果集 while (rs.next()) { Integer id = rs.getInt("id"); String userName = rs.getString("user_name"); String realName = rs.getString("real_name"); String password = rs.getString("password"); Integer did = rs.getInt("d_id"); user.setId(id); user.setUserName(userName); user.setRealName(realName); user.setPassword(password); user.setDId(did); System.out.println(user); } 复制代码
具体的操作步骤是,首先在pom.xml中引入MySQL的驱动依赖,注意MySQL数据库的版本
- Class.forName注册驱动
- 获取一个Connection对象
- 创建一个Statement对象
- execute()方法执行SQL语句,获取ResultSet结果集
- 通过ResultSet结果集给POJO的属性赋值
- 最后关闭相关的资源
这种实现方式首先给我们的感觉就是操作步骤比较繁琐,在复杂的业务场景中会更麻烦。尤其是我们需要自己来维护管理资源的连接,如果忘记了,就很可能造成数据库服务连接耗尽。同时我们还能看到具体业务的SQL语句直接在代码中写死耦合性增强。每个连接都会经历这几个步骤,重复代码很多,总结上面的操作的特点: 复制代码
- 代码重复
- 资源管理
- 结果集处理
- SQL耦合
针对这些问题我们可以自己尝试解决下
1.2 JDBC优化1.0
针对常规jdbc操作的特点,我们可以先从代码重复和资源管理方面来优化,我们可以创建一个工具类来专门处理这个问题 复制代码
public class DBUtils { private static final String JDBC_URL = "jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8&serverTimezone=UTC"; private static final String JDBC_NAME = "root"; private static final String JDBC_PASSWORD = "123456"; private static Connection conn; /** * 对外提供获取数据库连接的方法 * @return * @throws Exception */ public static Connection getConnection() throws Exception { if(conn == null){ try{ conn = DriverManager.getConnection(JDBC_URL,JDBC_NAME,JDBC_PASSWORD); }catch (Exception e){ e.printStackTrace(); throw new Exception(); } } return conn; } /** * 关闭资源 * @param conn */ public static void close(Connection conn ){ close(conn,null); } public static void close(Connection conn, Statement sts ){ close(conn,sts,null); } public static void close(Connection conn, Statement sts , ResultSet rs){ if(rs != null){ try { rs.close(); }catch (Exception e){ e.printStackTrace(); } } if(sts != null){ try { sts.close(); }catch (Exception e){ e.printStackTrace(); } } if(conn != null){ try { conn.close(); }catch (Exception e){ e.printStackTrace(); } } } } 复制代码
对应的jdbc操作代码可以简化如下
/** * * 通过JDBC查询用户信息 */ public void queryUser(){ Connection conn = null; Statement stmt = null; User user = new User(); ResultSet rs = null; try { // 注册 JDBC 驱动 // Class.forName("com.mysql.cj.jdbc.Driver"); // 打开连接 conn = DBUtils.getConnection(); // 执行查询 stmt = conn.createStatement(); String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1"; rs = stmt.executeQuery(sql); // 获取结果集 while (rs.next()) { Integer id = rs.getInt("id"); String userName = rs.getString("user_name"); String realName = rs.getString("real_name"); String password = rs.getString("password"); Integer did = rs.getInt("d_id"); user.setId(id); user.setUserName(userName); user.setRealName(realName); user.setPassword(password); user.setDId(did); System.out.println(user); } } catch (SQLException se) { se.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { DBUtils.close(conn,stmt,rs); } } /** * 通过JDBC实现添加用户信息的操作 */ public void addUser(){ Connection conn = null; Statement stmt = null; try { // 打开连接 conn = DBUtils.getConnection(); // 执行查询 stmt = conn.createStatement(); String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values('wangwu','王五','111',22,1001)"; int i = stmt.executeUpdate(sql); System.out.println("影响的行数:" + i); } catch (SQLException se) { se.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { DBUtils.close(conn,stmt); } } 复制代码
但是整体的操作步骤还是会显得比较复杂,这时我们可以进一步优化
1.3 JDBC优化2.0
我们可以针对DML操作的方法来优化,先解决SQL耦合的问题,在DBUtils中封装DML操作的方法
/** * 执行数据库的DML操作 * @return */ public static Integer update(String sql,Object ... paramter) throws Exception{ conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql); if(paramter != null && paramter.length > 0){ for (int i = 0; i < paramter.length; i++) { ps.setObject(i+1,paramter[i]); } } int i = ps.executeUpdate(); close(conn,ps); return i; } 复制代码
然后在DML操作的时候我们就可以简化为如下步骤
/** * 通过JDBC实现添加用户信息的操作 */ public void addUser(){ String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values(?,?,?,?,?)"; try { DBUtils.update(sql,"wangwu","王五","111",22,1001); } catch (Exception e) { e.printStackTrace(); } } 复制代码
显然这种方式会比最初的使用要简化很多,但是在查询处理的时候我们还是没有解决ResultSet结果集的处理问题,所以我们还需要继续优化
1.4 JDBC优化3.0
针对ResultSet的优化我们需要从反射和元数据两方面入手,具体如下 复制代码
/** * 查询方法的简易封装 * @param sql * @param clazz * @param parameter * @param <T> * @return * @throws Exception */ public static <T> List<T> query(String sql, Class clazz, Object ... parameter) throws Exception{ conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql); if(parameter != null && parameter.length > 0){ for (int i = 0; i < parameter.length; i++) { ps.setObject(i+1,parameter[i]); } } ResultSet rs = ps.executeQuery(); // 获取对应的表结构的元数据 ResultSetMetaData metaData = ps.getMetaData(); List<T> list = new ArrayList<>(); while(rs.next()){ // 根据 字段名称获取对应的值 然后将数据要封装到对应的对象中 int columnCount = metaData.getColumnCount(); Object o = clazz.newInstance(); for (int i = 1; i < columnCount+1; i++) { // 根据每列的名称获取对应的值 String columnName = metaData.getColumnName(i); Object columnValue = rs.getObject(columnName); setFieldValueForColumn(o,columnName,columnValue); } list.add((T) o); } return list; } /** * 根据字段名称设置 对象的属性 * @param o * @param columnName */ private static void setFieldValueForColumn(Object o, String columnName,Object columnValue) { Class<?> clazz = o.getClass(); try { // 根据字段获取属性 Field field = clazz.getDeclaredField(columnName); // 私有属性放开权限 field.setAccessible(true); field.set(o,columnValue); field.setAccessible(false); }catch (Exception e){ // 说明不存在 那就将 _ 转换为 驼峰命名法 if(columnName.contains("_")){ Pattern linePattern = Pattern.compile("_(\\w)"); columnName = columnName.toLowerCase(); Matcher matcher = linePattern.matcher(columnName); StringBuffer sb = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sb, matcher.group(1).toUpperCase()); } matcher.appendTail(sb); // 再次调用复制操作 setFieldValueForColumn(o,sb.toString(),columnValue); } } } 复制代码
封装了以上方法后我们的查询操作就可以简化为
/** * * 通过JDBC查询用户信息 */ public void queryUser(){ try { String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = ?"; List<User> list = DBUtils.query(sql, User.class,2); System.out.println(list); } catch (Exception e) { e.printStackTrace(); } } 复制代码
这样一来我们在操作数据库中数据的时候就只需要关注于核心的SQL操作了。当然以上的设计还比较粗糙,,这时Apache 下的 DbUtils是一个很好的选择