1. 一个最基本的通过JDBC操作数据库示例:
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql:///mybatis-leanring?user=root&password=root";
Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
sql = "SELECT * FROM USER";
stmt.executeUpdate(sql);
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
System.out.println(rs.getString(1) + "\t" + rs.getString(2));
}
通过JDBC操作数据库有一些问题:
- 数据库连接硬编码
- 频繁创建释放连接,对系统资源消耗较大
- JAVA代码与SQL代码耦合在一起,不便于维护
- 结果集提取很麻烦
如何来解决这些问题呢?
针对以上问题,我们可以做如下尝试:
- 数据库连接放到配置文件中
- 用连接池来管理connection
- sql文件放到配置文件中
- 用java 反射与内省机制来自动提取结果集
2. 框架设想
- 用一个SqlMapConfig.xml来放置数据库连接信息配置文件
配置文件解析出来,用一个类来存放配置信息,姑且叫他Configuration,如何解析呢,dom4j可以实现,我们用一个SqlSessionFactoryBuilder来解析 - 连接池选用ComboPooledDataSource,这样连接我们就交由连接池来处理
- sql相关代码放到xxMapper.xml中
但是sql可能有很多,比如查询用户,新增商品,更新订单状态等,不如直接按照一个实体一个mapper分开放置,这样便于管理。所以我们应该有很多个xxxMapper.xml文件,那每个Mapper.xml搞一个namespace来区分吧。
每个mapper.xml中就放置一个实体的处理,比如GoodsMapper.xml:selectList, selectOne, deleteByPrimaryKey, updateByPrimaryKey等都是操作商品的,用<select>,<update><delete><insert>分别来配置对应的sql,每个节点还应该有个id来区分不同的sql配置块。
Sql执行可能有参数,那搞一个ParameterHandler来专门负责解析并设置参数吧 - 结果集提取搞一个ResultSetHandler来处理
再想想,我们操作数据库通常来说不外乎增删改查,我们能否提供一个通用工具来处理呢,比如sqlUtil.selectList,sqlUtil.selectOne, sqlUtil.update, sqlUtil.delete等,就叫做SqlSession吧
于是:
src/main/resources:
SqlMapConfig.xml —— 数据库配置文件
UserMapper.xml —— SQL配置文件
java/main/java:
config:
Configuration.java
XmlConfigBuilder.java —— 解析SqlMapConfig.xml文件
MappedStatement.java —— mapper.xml中的单个sql配置块对应的实体
XmlMapperBuilder.java —— 解析mapper.xml文件
SqlCommandType.java —— 用于区分不同的SQL类型,例如:SELECT,INSERT,UPDATE,DELETE
sqlSession:
SqlSessionFactoryBuilder.java —— 解析配置,并生成SessionSessionFactory
SqlSessionFactory —— 生产SqlSession的工厂
SqlSession —— 操作数据库的对象
handler:
ParameterHandler.java —— 解析并设置参数
ResultSetHandler.java —— 提取结果集
如何查询呢,
通过[namespace].[id]就可以定位到具体的sql,然后执行即可。
例如查询用户
sqlSession.selectList("com.test.User.selectList")
每个操作都用sqlSession.(statmentId, params)来执行的话,又出现了硬编码,
Mapper.xml是已知的,Mapper.xml中的sql是已知的,也即是namespace.id是已知的,同时,执行sql不外乎crud,那不如搞个接口,来配置这些项目,然后通过动态代理来统一处理,解决这个问题。
UserMapper.xml
List<User> selectList(User user);
User selectOne(User user);
int update(User user);
int delete(int id);
void insert(User user);
SqlSession.java
<T> T getMapper(Class<T> t);
当使用动态代理时,对代理对象调用任意方法,将执行handler中的invoke方法,这样就可以通过反射来统一处理。
public <T> T getMapper(Class<?> mapperClass) {
// 使用JDK动态代理来为Dao接口生成代理对象,并返回
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),
new Class[] {mapperClass}, (proxy, method, args) -> {
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
MappedStatement mappedStatement =
configuration.getMappedStatementMap().get(statementId);
SqlCommandType commandType = mappedStatement.getSqlCommandType();
switch (commandType) {
case SELECT:
// 通过返回类型判断是否是List类型
Class returnType = method.getReturnType();
if (List.class.isAssignableFrom(returnType)) {
// 判断是否进行了 泛型类型参数化
List<Object> objects = selectList(statementId, args);
return objects;
}
return selectOne(statementId, args);
case INSERT:
case DELETE:
case UPDATE:
return update(statementId, args);
default:
return null;
}
});
return (T) proxyInstance;
}
如何支持事务?
直接采用jdbc的事务管理
try {
connection.setAutoCommit(false);
// do something here
connnection.commit();
}
当然,我们提供由SqlSession来操作事务的方法,这样使用方才能方便的使用事务。
SqlSession调用Executor的相关方法, Executor中有个单例获取的Connection,最终的事务操作落在connection上面。