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 = ?";
// 获取预处理statement
preparedStatement = 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");
// 封装User
User 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核心配置文件:
<configuration>
<dataSource>
<property name="driverClassname" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="123456789" />
dataSource>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
mappers>
configuration>
创建映射配置文件:
获取某个sql语句的唯一标示statementId:namespace.id 如:user.selectList
<mapper namespace="user">
<select id="selectList" resultType="com.xc.pojo.User" >
select * from user
select>
<select id="selectOne" resultType="com.xc.pojo.User" parameterType="com.xc.pojo.User" >
select * from user where id = #{id} and name = #{name}
select>
mapper>
User实体:
@Data
public class User {
private Integer id;
private String name;
}
pom.xml引入自定义框架
<dependency>
<groupId>com.xcgroupId>
<artifactId>own-mybatisartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
自定义框架本身
public class Resources {
public static InputStream getResourceAsStream(String path){
return Resources.class.getClassLoader().getResourceAsStream(path);
}
}
@Data
public class MapperStatement {
//唯一标识 statementId:namespace.id
private 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:statementId
private Map<String,MapperStatement> 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<Element> 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<Element> 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<Element> 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;
}
@Override
public SqlSession openSession() {
// 1.创建执行器对象-具体的包装jdbc的sql操作,关闭连接等
Executor simpleExecutor = new SimpleExecutor();
// 2.创建sqlSession对象-判断执行增删改查哪些操作等
return new DefaultSqlSession(configuration,simpleExecutor);
}
}
public interface SqlSession {
// 查询多个结果
<E> List<E> selectList(String statementId, Object param) throws Exception;
// 查询单个结果
<T> 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;
}
@Override
public <E> List<E> selectList(String statementId, Object param) throws Exception {
// 根据StatementId获取映射配置对象MapperStatement
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
// 然后将具体的查询操作委派给SimpleExecutor执行器
// 执行底层jdbc需要:1.数据库配置,2.sql配置信息
return executor.query(configuration,mapperStatement,param);
}
@Override
public <T> T selectOne(String statementId, Object param) throws Exception {
// 调用selectList()
List<Object> selectList = selectList(statementId, param);
if (selectList.size() == 1){
return (T) selectList.get(0);
}else if (selectList.size() > 1){
throw new Exception("返回数据不止一条!!!");
}else {
return null;
}
}
@Override
public void close() {
executor.close();
}
}
public interface Executor {
<E> List<E> 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;
@Override
public <E> List<E> 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.User
String parameterType = mapperStatement.getParameterType();
// 没有入参类型,表示sql没有参数,这里不需要?赋值
if (parameterType != null){
// 入参对象Class类
Class<?> parameterTypeClass = Class.forName(parameterType);
// 获取#{}参数字段list
List<ParameterMapping> 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<E> list = new ArrayList<>();
while (resultSet.next()){
// 通过
String resultType = mapperStatement.getResultType();
Class<?> resultTypeClass = Class.forName(resultType);
Object obj = resultTypeClass.newInstance();
// 结果集的元数据信息:字段名,字段值等
// resultSet: 一条结果集对应一张表的所有字段
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
//数据库字段名
String columnName = metaData.getColumnName(i);
// 字段的值
Object value = resultSet.getObject(columnName);
// columnName:数据库字段,而下方需要实体中的字段,如果两边不一样,则这需要有一个转化
// 获取读写方法即get、set方法
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
// 获取读方法-字段的set方法
Method writeMethod = propertyDescriptor.getWriteMethod();
// 反射为返回对象赋值
// 参数1:实例对象 参数2:要设置的值
writeMethod.invoke(obj,value);
}
list.add((E) obj);
}
return list;
}
/**
* @Description 获取?占位符的sql,以及保存#{}中的字段名称
*
* 1、将
private BoundSql getBoundSql(String sql) {
// 1.创建标记处理器:配合标记解析器完成标记的处理解析工作
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
// 2.创建标记解析器
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
// #{}占位符替换成? 2.解析替换的过程中 将#{}里面的值保存下来 ParameterMapping
String finalSql = genericTokenParser.parse(sql);
// #{}里面的值的一个集合 id username
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
return new BoundSql(finalSql, parameterMappings);
}
@Override
public void close() {
// 释放资源
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();
}
}
}
}
@Data
@AllArgsConstructor
public class BoundSql {
// 用?做占位符的sql语句
private String finalSql;
// 字段名称的集合
private List<ParameterMapping> 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<User> 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> 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;
}
@Override
public <E> List<E> selectList(String statementId, Object param) throws Exception {
// 根据StatementId获取映射配置对象MapperStatement
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
// 然后将具体的查询操作委派给SimpleExecutor执行器
// 执行底层jdbc需要:1.数据库配置,2.sql配置信息
return executor.query(configuration,mapperStatement,param);
}
@Override
public <T> T selectOne(String statementId, Object param) throws Exception {
// 调用selectList()
List<Object> selectList = selectList(statementId, param);
if (selectList.size() == 1){
return (T) selectList.get(0);
}else if (selectList.size() > 1){
throw new Exception("返回数据不止一条!!!");
}else {
return null;
}
}
@Override
public void close() {
executor.close();
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用JDK动态代理生成基于接口的对象
// 1、创建一个类(代理类),实现目标接口,实现所有的方法实现
// 2、动态代理类:代码运行期间生成的,而非编译期
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),
new Class[]{mapperClass}, new InvocationHandler() {
// proxy:生成的代理对象本身,很少用
// method:调用接口中哪个方法,则执行对应代理里的对应方法
// args:调用方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行底层JDBC
// 1.获取statementId
// ps:约定接口中的方法名要与
// 这样就可以通过接口获取statementId = namespace.id
String methodName = method.getName();// 方法名 - findAll
String className = mapperClass.getName();//接口类名 - com.xc.do.IUserDao
String statementId = className + "." + methodName;
// 2.判断调用sqlSession中CRUD的什么方法
// MapperStatement类添加属性sqlCommandType-sql增删改查类型
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
// select update delete insert
String sqlCommandType = mapperStatement.getSqlCommandType();
switch (sqlCommandType){
case "select" :
// 3.判断调用selectOne还是selectList
// 获取返回值类型,
Type genericReturnType = method.getGenericReturnType();
// ParameterizedType是Type的子接口,表示一个有参数的类型,例如Collection,Map等
if(genericReturnType instanceof ParameterizedType){
if(args != null) {
return selectList(statementId, args[0]);
}
return selectList(statementId, null);
}
return selectOne(statementId,args[0]);
case "update":
// 执行更新方法调用
break;
case "delete":
// 执行delete方法调用
break;
case "insert":
// 执行insert方法调用
break;
}
return null;
}
});
return (T) proxyInstance;
}
}
@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<User> all = userDao.findAll();
for (User user : all) {
System.out.println(user);
}
// 5.释放资源
sqlSession.close();
}