JDBC使用流程
代码示例
public class JDBCTest {public static void main(String[] args) throws Exception {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {// 加载数据库驱动Class.forName("com.mysql.jdbc.Driver");// 通过驱动管理类获取数据库链接connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis","root","123456789");// 定义sql语句?表示占位符String sql = "select * from user where name = ?";// 获取预处理statementpreparedStatement = connection.prepareStatement(sql);// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值preparedStatement.setString(1, "zhangsan");// 向数据库发出sql执行查询,查询出结果集resultSet = preparedStatement.executeQuery();// 遍历查询结果集while (resultSet.next()) {int id = resultSet.getInt("id");String username = resultSet.getString("name");// 封装UserUser user = new User();user.setId(id);user.setName(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();}}}}
}
问题分析
存在问题 | 解决思路 |
---|---|
数据库配置信息存在硬编码问题 | 使用配置文件 |
频繁创建、释放数据库连接问题 | 使用数据库连接池 |
SQL语句 | 使用配置文件 |
数据库配置信息存在硬编码问题 | 使用配置文件 |
主要分两部分,项目使用端:平常写代码所说的后台服务;持久层框架:即项目使用端引入的jar包
核心接口/类重点说明:
类名定义 | 角色定位 | 分工协作 |
---|---|---|
Resources | 资源辅助类 | 负责读取配置文件转化为输入流 |
Configuration | 数据库资源类 | 负责存储数据库连接信息 |
MappedStatement | SQL与结果集资源类 | 负责存储SQL映射定义、存储结果集映射定义 |
SqlSessionFactoryBuilder | 会话工厂构建者 | 负责解析配置文件,创建会话工厂SqlSessionFactory |
SqlSessionFactory | 会话工厂 | 负责创建会话SqlSession |
SqlSession | 会话 | 指派执行器Executor |
Executor | 执行器 | 负责执行SQL (配合指定资源Mapped Statement) |
项目使用端:
注意: sqlMapConfig.xml中引入mapper.xml是为了只读取一次配置文件,否则每个实体类会有一个mapper.xml,则需要读取很多次
自定义框架本身:
SqlSession接口定义以上方法,DefaultSqlSession来决定什么操作调用对应的sql执行器
项目使用端
创建sqlMapConfig核心配置文件:
创建映射配置文件:
获取某个sql语句的唯一标示statementId:namespace.id 如:user.selectList
User实体:
@Data
public class User {private Integer id;private String name;
}
pom.xml引入自定义框架
com.xc own-mybatis 1.0-SNAPSHOT
自定义框架本身
public class Resources {public static InputStream getResourceAsStream(String path){return Resources.class.getClassLoader().getResourceAsStream(path);}
}
@Data
public class MapperStatement {//唯一标识 statementId:namespace.idprivate String statementId;//返回值类型private String resultType;//参数类型private String parameterType;//sql语句private String sql;// 判断当前是什么操作的一个属性-增删改查private String sqlCommandType;
}
@Data
public class Configuration {//数据源对象private DataSource dataSource;//map.xml对象集合 key:statementIdprivate Map mapperStatementMap = new HashMap<>();
}
public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream inputStream) throws Exception {//1.解析配置文件,封装容器对象:专门解析核心配置文件的解析类XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();Configuration configuration = xmlConfigBuilder.parse(inputStream);//2.创建SqlSessionFactory工厂对象return new DefaultSqlSessionFactory(configuration);}}
public class XMLConfigBuilder {private Configuration configuration;public XMLConfigBuilder() {this.configuration = new Configuration();}// 使用dom4j+xpath解析public Configuration parse(InputStream inputStream) throws Exception {//将xml转化为Document对象Document document = new SAXReader().read(inputStream);//获取跟节点,对于sqlMapConfig.xml来说就是标签Element rootElement = document.getRootElement();// -------------解析数据库配置文件----------------// "//"表示从匹配选择的当前节点,而不考虑它们的位置// 即这里获取数据源url用户密码信息// 例: List propertyList = rootElement.selectNodes("//property");Properties properties = new Properties();for (Element element : propertyList) {// 获取标签中,name和value属性的值String name = element.attributeValue("name");String value = element.attributeValue("value");properties.setProperty(name,value);}// 创建数据源对象DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(properties.getProperty("driverClassName"));dataSource.setUrl(properties.getProperty("url"));dataSource.setUsername(properties.getProperty("username"));dataSource.setPassword(properties.getProperty("password"));// 将创建好的数据源添加到Configuration对象中configuration.setDataSource(dataSource);// -------------解析映射配置文件----------------/*1.获取映射配置文件路径2.根据路径进行映射文件的加载解析3.封装到MapperStatement,存入configuration的map集合中*/// 例: List mapperList = rootElement.selectNodes("//mapper");for (Element element : mapperList) {String resource = element.attributeValue("resource");InputStream resourceAsStream = Resources.getResourceAsStream(resource);// XMLMapperBuilder 专门解析映射配置文件的对象-最后会存入configuration的map集合对象中XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);xmlMapperBuilder.parse(resourceAsStream);}return configuration;}
}
public class XMLMapperBuilder {private Configuration configuration;public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}public void parse(InputStream resourceAsStream) throws Exception {// 将输入流转化为Document对象,并获取跟节点Document document = new SAXReader().read(resourceAsStream);Element rootElement = document.getRootElement();// 例:String namespace = rootElement.attributeValue("namespace");/* 例:*/List selectList = rootElement.selectNodes("//select");for (Element element : selectList) {String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String parameterType = element.attributeValue("parameterType");String sql = element.getTextTrim();// 封装MapperStatement对象MapperStatement mapperStatement = new MapperStatement();String statementId = namespace + "." + id;mapperStatement.setStatementId(statementId);mapperStatement.setParameterType(parameterType);mapperStatement.setResultType(resultType);mapperStatement.setSql(sql);//第四节 自定义框架_优化才会用到mapperStatement.setSqlCommandType("select");// 添加到configurations的map集合中configuration.getMapperStatementMap().put(statementId,mapperStatement);}}
}
public interface SqlSessionFactory {// 创建SqlSession对象SqlSession openSession();}
public class DefaultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {// 1.创建执行器对象-具体的包装jdbc的sql操作,关闭连接等Executor simpleExecutor = new SimpleExecutor();// 2.创建sqlSession对象-判断执行增删改查哪些操作等return new DefaultSqlSession(configuration,simpleExecutor);}
}
public interface SqlSession {// 查询多个结果 List selectList(String statementId, Object param) throws Exception;// 查询单个结果 T selectOne(String statementId, Object param) throws Exception;// 清理资源void close();}
public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor;public DefaultSqlSession(Configuration configuration, Executor executor) {this.configuration = configuration;this.executor = executor;}@Overridepublic List selectList(String statementId, Object param) throws Exception {// 根据StatementId获取映射配置对象MapperStatementMapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);// 然后将具体的查询操作委派给SimpleExecutor执行器// 执行底层jdbc需要:1.数据库配置,2.sql配置信息return executor.query(configuration,mapperStatement,param);}@Overridepublic T selectOne(String statementId, Object param) throws Exception {// 调用selectList()List
public interface Executor { List query(Configuration configuration, MapperStatement mapperStatement, Object param) throws Exception;void close();
}
public class SimpleExecutor implements Executor{private Connection connection = null;private PreparedStatement preparedStatement = null;private ResultSet resultSet = null;@Overridepublic List query(Configuration configuration, MapperStatement mapperStatement, Object param) throws Exception {// 1.加载驱动,获取数据库连接connection = configuration.getDataSource().getConnection();// 2.获取preparedStatement预编译对象// 从mapperStatement中获取sql语句// 例:select * from user where id = #{id} and username = #{username}String sql = mapperStatement.getSql();// 1)需要转化为:select * from user where id = ? and username = ?// 2)需要将替换的值保存下来,后续?赋值需要用到BoundSql boundSql = getBoundSql(sql);String finalSql = boundSql.getFinalSql();preparedStatement = connection.prepareStatement(finalSql);// 3.根据入参赋值?// 获取入参的全限定类名 com.xc.pojo.UserString parameterType = mapperStatement.getParameterType();// 没有入参类型,表示sql没有参数,这里不需要?赋值if (parameterType != null){// 入参对象Class类Class> parameterTypeClass = Class.forName(parameterType);// 获取#{}参数字段listList parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {// 获取字段名称ParameterMapping parameterMapping = parameterMappingList.get(i);String paramName = parameterMapping.getContent();// 通过反射获取入参对象的Field属性Field field = parameterTypeClass.getDeclaredField(paramName);// 禁用安全检查,不用排除private,效率提升好多倍field.setAccessible(true);// param为User对象,通过属性反射获取到user对象id和name的属性值Object value = field.get(param);// 赋值占位符,占位符?数字从1开始preparedStatement.setObject(i + 1, value);}}// 4.执行sql,发起查询resultSet = preparedStatement.executeQuery();// 5.处理返回结果集List list = new ArrayList<>();while (resultSet.next()){// 通过
@Data
@AllArgsConstructor
public class BoundSql {// 用?做占位符的sql语句private String finalSql;// 字段名称的集合private List parameterMappingList;
}
@Data
@AllArgsConstructor
public class ParameterMapping {// 保存#{}中对于的字段名称private String content;
}
@Test
public void test1() throws Exception {// 1.根据配置文件的路径,加载成字节输入流,存到内存中 注意:配置文件还未解析InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");// 2.解析配置文件,封装到Configuration对象,创建sqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 3.生产sqlSession 创建执行器对象SqlSession sqlSession = sqlSessionFactory.openSession();// 4.调用sqlSession方法User user = new User();user.setId(100);user.setName("zhangsan");User userOne = sqlSession.selectOne("user.selectOne", user);System.out.println("查询单个用户:"+userOne);List userList = sqlSession.selectList("user.selectList",null);System.out.println("查询所有用户:"+userList.toString());// 5.释放资源sqlSession.close();
}
输出结果:
查询单个用户:User(id=100, name=zhangsan)
查询所有用户:[User(id=100, name=zhangsan), User(id=120, name=Lisi)]
在sqlSession中添加方法
public interface SqlSession {...// 生成代理对象 T getMapper(Class> mapperClass);}
对应实现类方法
public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor;public DefaultSqlSession(Configuration configuration, Executor executor) {this.configuration = configuration;this.executor = executor;}@Overridepublic List selectList(String statementId, Object param) throws Exception {// 根据StatementId获取映射配置对象MapperStatementMapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);// 然后将具体的查询操作委派给SimpleExecutor执行器// 执行底层jdbc需要:1.数据库配置,2.sql配置信息return executor.query(configuration,mapperStatement,param);}@Overridepublic T selectOne(String statementId, Object param) throws Exception {// 调用selectList()List
@Test
public void test2() throws Exception {// 1.根据配置文件的路径,加载成字节输入流,存到内存中 注意:配置文件还未解析InputStream resourceAsSteam = Resources.getResourceAsStream("sqlMapConfig.xml");// 2.解析了配置文件,封装了Configuration对象 2.创建sqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);// 3.生产sqlSession 创建了执行器对象SqlSession sqlSession = sqlSessionFactory.openSession();// 4.调用sqlSession方法IUserDao userDao = sqlSession.getMapper(IUserDao.class);List all = userDao.findAll();for (User user : all) {System.out.println(user);}// 5.释放资源sqlSession.close();
}
手写源码框架项目: https://gitee.com/xuchang614/own-mybatis
测试框架项目: https://gitee.com/xuchang614/own-mybatis-test