1.1分析JDBC操作数据库问题
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 通过驱动获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dev?characterEncoding=UTF-8",
"****", "****");
// 定义sql语句,?标识占位符
String sql = "select * from `user` where name = ?";
// 获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 设置参数,从1开始
preparedStatement.setString(1, "Jack");
// 执行sql,查询数据获取结果
resultSet = preparedStatement.executeQuery();
User user = new User();
// 遍历查询结果
while (resultSet.next()) {
long id = resultSet.getLong("id");
String name = resultSet.getString("name");
Integer age = resultSet.getInt("age");
String email = resultSet.getString("email");
// 封装user
user.setId(id);
user.setName(name);
user.setAge(age);
user.setEmail(email);
}
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 资源释放
if (Objects.nonNull(resultSet)) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (Objects.nonNull(preparedStatement)) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (Objects.nonNull(connection)) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
JDBC存在的问题
1、数据库配置信息存在硬编码(驱动、链接信息)
2、数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能
3、sql语句在代码中硬编码,造成代码不易维护,sql变动需要改变 java代码
4、 使⽤preparedStatement向占位符传参数存在硬编码,因为sql语句的where条件不确定,修改sql还要修改代码,系统不易维护
5、 对结果集解析存在硬编码(列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析⽐较⽅便
1.2JDBC问题解决思路
1、配置信息抽取到配置文件
2、使用连接池优化数据库连接
3、sql语句使用配置文件
4、参数设置和封装结果集使用反射、内省
1.3自定义ORM框架设计思路(本质就是对JDBC的封装)
-
使用端提供两部分配置文件
1、数据库配置信息(sqlMapConfig.xml)
2、sql映射信息,包含参数类型、返回值类型(mapper.xml)
-
自定义ORM框架
1、加载配置文件,读取成流存储在内存中
1)、创建Resources类型 方法getResourceAsStream(String resource)
2、创建两个JavaBean(容器对象)存储配置文件解析出来的内容
1)、Configuration 核心配置类,存放sqlMapConfig.xml解析出来的内容
2)、MappedStatement 映射配置类,存放mapper.xml解析出来的内容
3、解析配置文件(使用dom4j)
创建SqlSessionFactoryBuilder类 方法builde(InputStream in)
1)、使用dom4j解析配置文件,将解析出来的内容封装到容器对象中
2)、创建解析sqlMapConfig.xml的构建类XMLConfigBuilder
3)、创建解析mapper.xml的构建类XMLMapperBuilder
4、创建SqlSessionFactory接口及默认实现类DefaultSqlSessionFactory 方法openSession() 生产sqlSession(会话对象)
5、创建SqlSession接口及默认实现类DefaultSqlSession
(1)、定义CRUD的操作方法 selectList()、selectOne()、update()、delete()
6、创建Executor接口及实现类SimpleExecutor 方法query(Configuration cf,MappedStatement ms,Object... params)执行jdbc代码
1.4自定义ORM实现
1.4.1使用端
sqlMapConfig.xml
<configuration>
<!--数据库配置信息-->
<dataSource>
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/dev?characterEncoding=UTF-8"></property>
<property name="username" value="****"></property>
<property name="password" value="****"></property>
</dataSource>
<!--存放mapper.xml的全路径-->
<mapper resource="UserMapper.xml"></mapper>
</configuration>
mapper.xml
<mapper namespace="com.qiyuan.mapper.UserMapper">
<!--sql的唯一标识:namespace.id来组成:statementId-->
<select id="listUsers" resultType="com.qiyuan.entity.User">
select * from user
</select>
<!--根据条件查询-->
<select id="getUser" resultType="com.qiyuan.entity.User" parameterType="com.qiyuan.entity.User">
select * from user where id = #{id} and name = #{name}
</select>
<insert id="insert" parameterType="com.qiyuan.entity.User">
insert into user (name,age,email) values (#{name},#{age},#{email})
</insert>
<update id="update" parameterType="com.qiyuan.entity.User">
update user
set name = #{name},
email = #{email}
where id = #{id}
</update>
<delete id="delete" parameterType="com.qiyuan.entity.User">
delete from user
where id = #{id}
</delete>
</mapper>
1.4.2自定义ORM框架端
Resources
public class Resources {
/**
* 根据配置文件路径加载配置文件,读取成流存储在内存中
*
* @param path
* @return java.io.InputStream
* @author qiyuan
* @date 2022/1/2 21:16
*/
public static InputStream getResourceAsStream(String path) {
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}
Configuration
@Data
public class Configuration {
/**
* 数据源
*/
private DataSource dataSource;
/**
* mapper映射配置类key:statementid value:封装好的mappedStatement对象
*/
private Map<String,MappedStatement> mappedStatementMap = new HashMap<>(16);
}
MappedStatement
@Data
public class MappedStatement {
/**
* id标识
*/
public String id;
/**
* 参数类型
*/
public String parameterType;
/**
* 返回值类型
*/
public String resultType;
/**
* sql语句
*/
public String sql;
/**
* sql类型 select、insert、update、delete
*/
public String sqlType;
}
SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
// 1.使用dom4j解析配置文件,将解析出来的内容封装到容器对象中
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parse(inputStream);
// 2.创建SqlSessionFactory 生产sqlSession会话对象
DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}
XMLConfigBuilder
public class XMLConfigBuilder {
private Configuration configuration;
public XMLConfigBuilder() {
this.configuration = new Configuration();
}
public Configuration parse(InputStream inputStream) throws DocumentException {
// 1.解析sqlMapConfig.xml
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
// 解析出所有的property
List<Node> propertyList = rootElement.selectNodes("//property");
// 配置属性就是key-value形式
Properties properties = new Properties();
propertyList.forEach(node -> {
Element element = (Element) node;
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name,value);
});
// 配置druid连接池
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(properties.getProperty("driverClass"));
dataSource.setUrl(properties.getProperty("jdbcUrl"));
dataSource.setUsername(properties.getProperty("username"));
dataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(dataSource);
// 2.解析mapper.xml
List<Node> mapperNodeList = rootElement.selectNodes("//mapper");
for (Node node : mapperNodeList) {
Element element = (Element) node;
// 解析出来的mapper映射文件路径
String mapperPath = element.attributeValue("resource");
// 根据路径加载成流
InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
// 创建mapper映射文件解析类
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
// 解析mapper映射文件
xmlMapperBuilder.parse(resourceAsStream);
}
return configuration;
}
}
XMLMapperBuilder
public class XMLMapperBuilder {
private Configuration configuration;
private static final List<String> sqlTypeList = new ArrayList<>(Arrays.asList(Constant.SELECT, Constant.INSERT, Constant.UPDATE, Constant.DELETE));
public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
public void parse(InputStream inputStream) throws DocumentException {
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
// 获取namespace
String namespace = rootElement.attributeValue("namespace");
// mapper映射文件中所有查询配置信息,并构建MappedStatement
sqlTypeList.forEach(sqlType -> {
List<Node> nodeList = rootElement.selectNodes(sqlType);
builderMappedStatement(namespace, nodeList, sqlType);
});
}
/**
* 构建MappedStatement
*
* @param namespace
* @param selectNodeList
* @param sqlType
* @author qiyuan
* @date 2022/1/2 23:24
*/
private void builderMappedStatement(String namespace, List<Node> selectNodeList, String sqlType) {
for (Node node : selectNodeList) {
Element element = (Element) node;
// id值,方法名(select id="xxx")
String id = element.attributeValue("id");
// 参数类型
String parameterType = element.attributeValue("parameterType");
// 返回值类型
String resultType = element.attributeValue("resultType");
// sql
String sqlText = element.getTextTrim();
// 绑定mappedStatement
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setParameterType(parameterType);
mappedStatement.setResultType(resultType);
mappedStatement.setSql(sqlText);
mappedStatement.setSqlType(sqlType);
String key = namespace + "." + id;
configuration.getMappedStatementMap().put(key, mappedStatement);
}
}
}
SqlSessionFactory和DefaultSqlSessionFactory
public interface SqlSessionFactory {
SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
SqlSession和DefaultSqlSession
public interface SqlSession {
<E> List<E> selectList(String statementId, Object... params) throws Exception;
<T> T selectOne(String statementId, Object... params) throws Exception;
int insert(String statementId, Object... params) throws Exception;
int update(String statementId, Object... params) throws Exception;
int delete(String statementId, Object... params) throws Exception;
<T> T getMapper(Class<?> aClass);
}
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private SimpleExecutor simpleExecutor;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
this.simpleExecutor = new SimpleExecutor();
}
@Override
public <E> List<E> selectList(String statementId, Object... params) throws Exception {
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>) list;
}
@Override
public <T> T selectOne(String statementId, Object... params) throws Exception {
List<Object> objects = selectList(statementId, params);
if (Objects.isNull(objects) || objects.size() == 0) {
return null;
}
if (objects.size() != 1) {
throw new RuntimeException("数据返回结果集过多");
}
return (T) objects.get(0);
}
@Override
public int insert(String statementId, Object... params) throws Exception {
return update(statementId, params);
}
@Override
public int update(String statementId, Object... params) throws Exception {
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return simpleExecutor.update(configuration, mappedStatement, params);
}
@Override
public int delete(String statementId, Object... params) throws Exception {
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return simpleExecutor.update(configuration, mappedStatement, params);
}
@Override
public <T> T getMapper(Class<?> aClass) {
// JDK动态代理为Mapper接口生成代理类
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{aClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取执行方法名
String name = method.getName();
// 获取代理类全限定类名
String className = method.getDeclaringClass().getName();
// statementId = className.name
String statementId = className + "." + name;
// 获取方法返回值
Type genericReturnType = method.getGenericReturnType();
// 获取mappedStatement
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
switch (mappedStatement.getSqlType()) {
case Constant.SELECT:
// 判断返回值是否是泛型
if (genericReturnType instanceof ParameterizedType) {
List<Object> list = selectList(statementId, args);
return list;
}
Object obj = selectOne(statementId, args);
return obj;
case Constant.INSERT:
return insert(statementId, args);
case Constant.UPDATE:
return update(statementId, args);
case Constant.DELETE:
return delete(statementId, args);
default:
return null;
}
}
});
return (T) proxyInstance;
}
}
Executor和SimpleExecutor
public interface Executor {
<E> List<E> query(Configuration configuration, MappedStatement ms, Object... params) throws Exception;
int update(Configuration configuration, MappedStatement ms, Object... params) throws Exception;
int delete(Configuration configuration, MappedStatement ms, Object... params) throws Exception;
}
public class SimpleExecutor implements Executor {
@Override
public <E> List<E> query(Configuration configuration, MappedStatement ms, Object... params) throws Exception {
// 1.获取预处理对象preparedStatement,并设置参数
PreparedStatement preparedStatement = getPreparedStatement(configuration, ms, params);
// 2.执行sql
ResultSet resultSet = preparedStatement.executeQuery();
// 3.封装返回结果集
// 获取返回值Class
Class<?> resultClassType = getClassType(ms.getResultType());
List<Object> list = new ArrayList<>();
while (resultSet.next()) {
// 获取元数据
ResultSetMetaData metaData = resultSet.getMetaData();
// 获取返回类型实例
Object obj = resultClassType.newInstance();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
// 获取字段名称
String columnName = metaData.getColumnName(i);
// 获取字段值
Object value = resultSet.getObject(columnName);
// 使用内省,封装数据库表和实体的对应关系
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClassType);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(obj, value);
}
list.add(obj);
}
return (List<E>) list;
}
@Override
public int update(Configuration configuration, MappedStatement ms, Object... params) throws Exception {
// 1.获取预处理对象preparedStatement,并设置参数
PreparedStatement preparedStatement = getPreparedStatement(configuration, ms, params);
return preparedStatement.executeUpdate();
}
@Override
public int delete(Configuration configuration, MappedStatement ms, Object... params) throws Exception {
// 1.获取预处理对象preparedStatement,并设置参数
PreparedStatement preparedStatement = getPreparedStatement(configuration, ms, params);
return preparedStatement.executeUpdate();
}
/**
* 获取预处理对象preparedStatement,并设置参数
*
* @param configuration
* @param ms
* @param params
* @return java.sql.PreparedStatement
* @author qiyuan
* @date 2022/1/6 14:50
*/
private PreparedStatement getPreparedStatement(Configuration configuration, MappedStatement ms, Object... params) throws Exception {
// 1.获取数据库连接
Connection connection = configuration.getDataSource().getConnection();
// 2.获取原始sql,对原始sql进行解析转换,同时对#{}里面的值进行存储id,name
// select * from user where id = #{id} and name = #{name}
// select * from user where id = ? and name = ?
String sql = ms.getSql();
BoundSql boundSql = getBoundSql(sql);
// 3.获取预处理对象preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getContent());
// 4.设置参数
String parameterType = ms.getParameterType();
// 获取参数Class
Class<?> parameterClassType = getClassType(parameterType);
// 获取参数集合
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
// 获取参数对象(#{}里面解析出来的值对象)
ParameterMapping parameterMapping = parameterMappingList.get(i);
// 获取参数(#{}里面解析出来的值)
String content = parameterMapping.getContent();
// 反射获取实例字段
Field declaredField = parameterClassType.getDeclaredField(content);
// 设置可以操作私有属性
declaredField.setAccessible(true);
// 获取实例属性值
Object o = declaredField.get(params[0]);
// 设置参数,下标从1开始
preparedStatement.setObject(i + 1, o);
}
return preparedStatement;
}
/**
* 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
*
* @param sql
* @return com.qiyuan.config.BoundSql
* @author qiyuan
* @date 2022/1/4 11:39
*/
private BoundSql getBoundSql(String sql) {
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
String parseSql = genericTokenParser.parse(sql);
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
return boundSql;
}
/**
*
* @author qiyuan
* @date 2022/1/6 17:19
* @param fullPath
* @return java.lang.Class<?>
*/
private Class<?> getClassType(String fullPath) throws ClassNotFoundException {
if (fullPath == null) {
return null;
}
return Class.forName(fullPath);
}
}
BoundSql
@Data
@AllArgsConstructor
public class BoundSql {
/**
* 解析之后的sql
*/
private String content;
private List<ParameterMapping> parameterMappingList;
}
1.5自定义ORM优化
通过自定义ORM,我们解决了JDBC操作数据库带来的一些问题
-
1、数据库配置信息存在硬编码(驱动、链接信息)
-
2、数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能
-
3、sql语句在代码中硬编码,造成代码不易维护,sql变动需要改变 java代码
-
4、 使⽤preparedStatement向占位符传参数存在硬编码,因为sql语句的where条件不确定,修改sql还要修改代码,系统不易维护
-
5、 对结果集解析存在硬编码(列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析⽐较⽅便
现在我们自定义ORM还有什么问题?
-
1、mapper的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅法)
-
2、mapper的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statementId硬编码
解决方案
是不是可以不需要mapper实现类?如果可以不就不存在以上问题了吗
使用代理模式,创建接口的代理对象就可以解决
在SqlSession里面添加getMapper方法
public interface SqlSession {
<T> T getMapper(Class<?> aClass);
}
实现
@Override
public <T> T getMapper(Class<?> aClass) {
// JDK动态代理为Mapper接口生成代理类
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{aClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取执行方法名
String name = method.getName();
// 获取代理类全限定类名
String className = method.getDeclaringClass().getName();
// statementId = className.name
String statementId = className + "." + name;
// 获取方法返回值
Type genericReturnType = method.getGenericReturnType();
// 获取mappedStatement
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
switch (mappedStatement.getSqlType()) {
case Constant.SELECT:
// 判断返回值是否是泛型
if (genericReturnType instanceof ParameterizedType) {
List<Object> list = selectList(statementId, args);
return list;
}
Object obj = selectOne(statementId, args);
return obj;
case Constant.INSERT:
return insert(statementId, args);
case Constant.UPDATE:
return update(statementId, args);
case Constant.DELETE:
return delete(statementId, args);
default:
return null;
}
}
});
return (T) proxyInstance;
}
使用端
@Test
public void testMapperInsert() throws Exception {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setName("启元");
user.setAge(18);
user.setEmail("qiyuan@123.com");
int count = userMapper.insert(user);
System.out.println("影响行数:" + count);
}
本文由博客一文多发平台 OpenWrite 发布!