为什么需要持久层框架?
首先我们先看看使用原生jdbc存在的问题?
public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { // 加载数据库驱动 Class.forName("com.mysql.jdbc.Driver"); // 通过驱动管理类获取数据库链接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8", "root", "root"); // 定义sql语句?表示占位符 String sql = "select * from user where username = ?"; // 获取预处理statement preparedStatement = connection.prepareStatement(sql); // 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值 preparedStatement.setString(1, "tom"); // 向数据库发出sql执⾏查询,查询出结果集 resultSet = preparedStatement.executeQuery(); // 遍历查询结果集 while (resultSet.next()) { int id = resultSet.getInt("id"); String username = resultSet.getString("username"); // 封装User user.setId(id); user.setUsername(username); } System.out.println(user); } } catch (Exception e) { e.printStackTrace(); } finally { // 释放资源 if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } }可以看出原始jdbc存在的问题如下:
数据库连接、创建、释放频繁造成资源浪,影响系统性能
sql语句卸载代码里,不易维护,也不好复用
使用preparedStatement向占位符传参存在硬编码,如果where条件变了,需要改sql
对结果集解析存在硬编码,加字段需要改sql并且改解析的代码,如果能将数据库查出的记录封装成pojo对象解析会比较方便
解决方法如下:
使用数据库连接池初始化连接资源
将sql语句抽取到xml配置文件中
使用反射等技术,自动将实体与表进行属性与字段的自动映射
自定义持久层框架本质就是对jdbc的一个封装和功能的增强,大概需要这几个步骤
创建配置文件
sqlMapConfig.xml :数据库配置信息
xxMapper.xml:sql语句信息
创建两个bean(容器对象):存放的就是对配置文件解析出来的内容
Configuration(核心配置类):存放sqlMapConfig.xml解析出来的内容
MappedStatement(映射配置类):存放xxMapper.xml解析出来的内容
创建类SqlSessionFactoryBuilder
使用dom4j解析配置文件,将内容封装到容器对象中
创建SqlSessionFactory对象,生产SqlSession
创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory。通过方法openSession生产SqlSession
创建接口SqlSession接口及实现类DefaultSqlSession。定义query、update、save接口
创建Executor接口及实现类SimpleExecutor,执行具体的jdbc代码操作。
开发完成后,第一阶段我们的dao层的实现是这样的:
public class UserDaoImpl implements UserDao { @Override public List<User> queryAll() throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException, DocumentException, PropertyVetoException, ClassNotFoundException { SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder(); SqlSessionFactory builder = sqlSessionFactoryBuilder.builder("sqlMapConfig.xml"); SqlSession sqlSession = builder.openSession(); List<User> objects = sqlSession.queryAll("User.selectList"); System.out.println(objects); return objects; } }可以看出还有两个明显需要改进的地方:
每个dao层的实现都需要写大段重复代码,userDaoImpl写一次,ProductDaoImpl写一次
每次调用queryAll都需要传入参数statementid
那么可以怎么解决呢?
SqlSessionFactory可以做成单例,方便调取。Dao层的实现类重复编码可以通过动态代理实现,动态生成Dao的实现类,Dao层只写接口就行了。
statementid可以通过约定名称对应的方式替代
代码如下:
原dao层实现:
public class UserDaoImpl implements UserDao { @Override public List<User> queryAll() throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException, DocumentException, PropertyVetoException, ClassNotFoundException { SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder(); SqlSessionFactory builder = sqlSessionFactoryBuilder.builder("sqlMapConfig.xml"); SqlSession sqlSession = builder.openSession(); List<User> objects = sqlSession.queryAll("User.selectList"); System.out.println(objects); return objects; } }动态代理:
public <T> T getMapper(Class mapperClass) { Object instance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("queryAll")) { String className = method.getDeclaringClass().getName(); String statementid = className + "." + method.getName(); List<User> objects = queryAll(statementid); return objects; } return null; } }); return (T) instance; }简单的封装就完成了。
总体项目地址:https://gitee.com/mmcLine/simple-mybatis