# Mybatis学习笔记
[TOC]
## JDBC问题分析
传统JDBC代码;
```java
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/learn_mybatis?characterEncoding=utf-8";
String userName = "root";
String password = "mysql";
Connection connection = DriverManager.getConnection(url, userName, password);
String sql = "select * from user where id = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "1");
ResultSet resultSet = pstmt.executeQuery();
List<User> userList = new ArrayList<User> (10);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("username");
User user = new User();
user.setId(id);
user.setUsername(name);
userList.add(user);
}
System.out.println(userList);
}
```
使用JDBC存在的问题分析:
- 数据库连接创建,释放频繁造成系统资源浪费,影响系统性能,连接数据库存在重复代码,导致重复编码
- sql语句在代码中存在硬编码,导致代码不易维护,实际应用中sql变化的可能性较大,sql改动需要改动java代码
- 使用PreparedStatement也存在硬编码的问题,导致代码不易维护
- 对结果集的解析也存在硬编码的问题,sql变化也会导致解析代码变化,系统不容易维护,如果每次返回成pojo对象解析比较方便
## JDBC问题解决方案
解决方案:
- 使用连接池进行连接数据库,并把连接数据库的硬编码配置写入配置文件
- sql语句也使用配置文件进行配置
- 使用反射或者内省自动对结果集进行返回
## 自定义Mybatis框架设计
**使用端**:
- 提供核心配置sqlMapConfig.xml文件,配置数据源信息以及引入sql配置文件
- 提供所有的Sql配置文件 mapper.xml文件
**框架端**:

### 自定义Mybaits框架代码实战
#### 使用端
**配置文件**:
使用端首先需要提供一个核心的配置文件,并引入相应的配置文件`sqlMapConfig.xml`
```xml
<configuration>
<dataSource>
<property name = "driver">com.mysql.cj.jdbc.Driver</property>
<property name = "url">jdbc:mysql://localhost:3306/learn_mybatis</property>
<property name = "user">root</property>
<property name = "password">mysql</property>
</dataSource>
<mapper>userMapper.xml</mapper>
</configuration>
```
相应的`userMapper.xml`文件内容,需要根据namespace的值进行与接口绑定
```xml
<mapper namespace="com.lagou.mapper.IUserMapper">
<select id="selectList" resultType = "com.lagou.pojo.User">
select id,username,password,birthday from user
</select>
<select id="selectOne" resultType = "com.lagou.pojo.User" paramType = "com.lagou.pojo.User">
select id,username from user where id = #{id} and username = #{username}
</select>
<insert id = "insertUser" resultType = "int" paramType = "com.lagou.pojo.User">
insert into user(id,username,password,birthday) values(#{id},#{username},#{password},#{birthday})
</insert>
<update id = "updateUser" resultType = "int" paramType = "com.lagou.pojo.User">
update user set username = #{username},password = #{password},birthday = #{birthday}
where id = #{id}
</update>
<delete id = "deleteUser" resultType = "int" paramType = "com.lagou.pojo.User">
delete from user where id = #{id}
</delete>
</mapper>
```
**测试类**:
```java
public class TestMybatis {
private IUserMapper userMapper;
@Before
public void before() throws Exception {
InputStream resourceAsSteam = Resource.getInputStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactroyBuilder().build(resourceAsSteam);
Sqlsession sqlsession = sqlSessionFactory.openSession();
userMapper = sqlsession.getMapper(IUserMapper.class);
}
/**
* 测试查询
*/
@Test
public void queryAll() {
List<User> userList = userMapper.selectList();
System.out.println(userList);
}
/**
* 测试插入
*/
@Test
public void insert() {
User user = new User();
user.setId(4);
user.setUsername("测试插入");
user.setPassword("123456");
user.setBirthday("2020-04-20");
System.out.println("插入记录前!");
int num = userMapper.insertUser(user);
System.out.println("成功插入"+num+"条记录");
}
@Test
public void update(){
User user = new User();
user.setId(4);
user.setUsername("修改测试");
user.setPassword("654321");
user.setBirthday("2020-04-27");
System.out.println("修改记录前!");
int num = userMapper.updateUser(user);
System.out.println("成功修改"+num+"条记录");
}
@Test
public void delete() {
User user = new User();
user.setId(4);
System.out.println("删除记录前!");
int num = userMapper.deleteUser(user);
System.out.println("成功删除"+num+"条记录");
}
@Test
public void testAllMethod() {
System.out.println("-------------------------第一次查询所有数据!");
queryAll();
insert();
System.out.println("-------------------------插入一条数据后查询所有数据!");
queryAll();
update();
System.out.println("-------------------------修改一条数据后查询所有数据!");
queryAll();
delete();
System.out.println("-------------------------删除一条数据后查询所有数据!");
queryAll();
}
}
```
#### 框架端
**解析配置文件**:
**Configuration类**:在mybatis中的配置是ALL IN ONE,所有的信息都在Configuration类中,所以这里我们也使用此种方式实现,声明一个Configuration类,存储相应的信息
```java
public class Configuration {
/**
* 数据源配置
*/
private DataSource source;
/**
* 将所有的Sql配置都缓存在该map中
*/
private Map<String, MapperStatement> mapperStatementMap = new HashMap<>();
}
```
**MapperStatement**:存储配置文件中sql的详细信息
```java
public class MapperStatement {
/**
* 相应SQL的id,id=nameSpace+Sql的id
*/
private String id;
/**
* 返回类型类名称
*/
private String resultType;
/**
* 参数类名称
*/
private String paramType;
/**
* 未转换前JDBC的sql
*/
private String sql;
/**
* 该SQL的类型,是否是增删改查
*/
private SqlCommandType sqlCommandType;
}
```
**SqlSessionFactroyBuilder类**:解析相应的XML文件到Configuration类中
```java
public class SqlSessionFactroyBuilder {
private Configuration configuration;
public SqlSessionFactroyBuilder() {
this.configuration = new Configuration();
}
public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
Element dataSource = rootElement.element("dataSource");
List<Element> datasourceList = dataSource.selectNodes("//property");
Properties properties = new Properties();
datasourceList.forEach(item ->{
properties.setProperty(item.attributeValue("name"), item.getTextTrim());
});
ComboPooledDataSource comboPooledDataSource = new
ComboPooledDataSource();
comboPooledDataSource.setDriverClass(properties.getProperty("driver"));
comboPooledDataSource.setJdbcUrl(properties.getProperty("url"));
comboPooledDataSource.setUser(properties.getProperty("user"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
configuration.setSource(comboPooledDataSource);
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
String textTrim = element.getTextTrim();
InputStream mapperInputStream = Resource.getInputStream(textTrim);
Document doc = new SAXReader().read(mapperInputStream);
Element mapperRootElement = doc.getRootElement();
String namespace = mapperRootElement.attributeValue("namespace");
List<Element> selectList = mapperRootElement.selectNodes("select|insert|update|delete");
for (Element selectNode : selectList) {
MapperStatement mapperStatement = new MapperStatement();
String id = selectNode.attributeValue("id");
String paramType = selectNode.attributeValue("paramType");
String resultType = selectNode.attributeValue("resultType");
String sql = selectNode.getTextTrim();
String key = namespace + "." + id;
mapperStatement.setResultType(resultType);
mapperStatement.setId(id);
mapperStatement.setSql(sql);
mapperStatement.setParamType(paramType);
String nodeTypeName = selectNode.getName();
//设置sql的类型,"select|insert|update|delete"
mapperStatement.setSqlCommandType(SqlCommandType.valueOf(nodeTypeName.toUpperCase(Locale.ENGLISH)));
configuration.getMapperStatementMap().put(key, mapperStatement);
}
}
return new DefaultSqlSessionFactory(configuration);
}
```
**定义SqlSession类与Executor类**:
SqlSession类中需要定义相应的增删改查的方法,而Executor则是对增删改查完成具体JDBC的封装,SqlSession调用Executor类完成最终功能实现
**SqlSession类**:SqlSession类中需要定义相应的增删改查的方法
```java
public interface Sqlsession {
/**
* 查询批量的数据
* @param statementId
* @param params
* @param <E>
* @return
* @throws Exception
*/
<E> List<E> selectList(String statementId, Object... params) throws Exception;
/**
* 查询单个数据
* @param statementId
* @param params
* @param <E>
* @return
* @throws Exception
*/
<E> E selectOne(String statementId, Object... params) throws Exception;
/**
* 使用动态代理获得最终代理后的mapper对象
* @param mapperClass
* @param <T>
* @return
*/
<T> T getMapper(Class<?> mapperClass);
/**
* 插入方法
* @param statement
* @param params
* @return
* @throws Exception
*/
int insert(String statement, Object... params) throws Exception;
/**
* 修改方法
* @param statement
* @param params
* @return
* @throws Exception
*/
int update(String statement, Object... params) throws Exception;
/**
* 删除方法
* @param statement
* @param params
* @return
* @throws Exception
*/
int delete(String statement, Object... params) throws Exception;
/**
* 关闭数据库连接
* @throws SQLException
*/
void close() throws SQLException;
}
```
**SqlSession实现类**:
```java
public class DefaultSqlSession implements Sqlsession {
/**
* 获取配置文件内容
*/
private Configuration configuration;
/**
* 配置执行器
*/
private Executor executor = new SimpleExecutor();
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementId, Object... params) throws Exception {
List<Object> query = executor.query(configuration, configuration.getMapperStatementMap().get(statementId),
params);
return (List<E>) query;
}
@Override
public <E> E selectOne(String statementId, Object... params) throws Exception {
List<Object> objects = selectList(statementId, params);
if (objects != null && objects.size() == 1) {
return (E) objects.get(0);
}
throw new RuntimeException("查询的数据为空或者多条!");
}
@Override
public int insert(String statementId, Object... params) throws Exception {
return executor.insert(configuration, configuration.getMapperStatementMap().get(statementId),
params);
}
@Override
public int update(String statementId, Object... params) throws Exception {
return executor.doUpdate(configuration, configuration.getMapperStatementMap().get(statementId),
params);
}
@Override
public int delete(String statementId, Object... params) throws Exception {
return executor.delete(configuration, configuration.getMapperStatementMap().get(statementId),
params);
}
@Override
public void close() throws SQLException {
executor.close();
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用JDK动态代理来为Dao接口生成代理对象,并返回
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, (proxy, method, args) -> {
// 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className+"."+methodName;
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
if (mapperStatement == null) {
throw new RuntimeException("找不到对应的mapperStatement!请检查配置!");
}
// 准备参数2:params:args
// 获取被调用方法的返回值类型
Type genericReturnType = method.getGenericReturnType();
SqlCommandType sqlCommandType = mapperStatement.getSqlCommandType();
Object obj = null;
switch (sqlCommandType) {
case SELECT:
// 判断是否进行了 泛型类型参数化
if(genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
obj = objects;
}
else{
obj = selectOne(statementId,args);
}
break;
case UPDATE:
obj = update(statementId, args);
break;
case DELETE:
obj = delete(statementId, args);
break;
case INSERT:
obj = insert(statementId, args);
break;
//此处不实现
case FLUSH:
break;
case UNKNOWN:
break;
default:
throw new IllegalStateException("Unexpected value: " + sqlCommandType);
}
return obj;
});
return (T) proxyInstance;
}
}
```
**Executor类**:封装JDBC类
```java
public interface Executor {
/**
* 底层封装jdbc查询方法
* @param configuration
* @param statement
* @param param
* @param <E>
* @return
* @throws Exception
*/
<E> List<E> query(Configuration configuration, MapperStatement statement, Object... param) throws Exception;
/**
* 底层封装insert方法
* @param configuration
* @param ms
* @param param
* @return
* @throws Exception
*/
int insert(Configuration configuration,MapperStatement ms, Object... param) throws Exception;
/**
* 底层封装update方法
* @param configuration
* @param ms
* @param param
* @return
* @throws Exception
*/
int doUpdate(Configuration configuration,MapperStatement ms, Object... param) throws Exception;
/**
* 底层封装delete方法
* @param configuration
* @param ms
* @param param
* @return
* @throws Exception
*/
int delete(Configuration configuration,MapperStatement ms, Object... param) throws Exception;
/**
* 关闭对应数据库连接
* @throws SQLException
*/
void close() throws SQLException;
}
```
**Executor实现类**:查询需要针对返回值类型进行反射封装,增删改统一走doUpdate方法就可以了,不需要要额外写其他内容
```java
public class SimpleExecutor implements Executor {
private Connection connection;
@Override
public <E> List<E> query(Configuration configuration, MapperStatement statement, Object... param) throws Exception {
List<E> result = new ArrayList<E>();
try {
connection = configuration.getSource().getConnection();
String sql = statement.getSql();
//将XML中的sql转换为JDBC执行的sql
BoundSql boundSql = getBoundSql(sql);
String paramType = statement.getParamType();
Class<?> aClass = null;
if (paramType != null) {
//获取参数类型
aClass = Class.forName(paramType);
}
String sqlText = boundSql.getSqlText();
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
PreparedStatement pstmt = connection.prepareStatement(sqlText);
for (int i = 0; parameterMappingList != null && i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
Field field = aClass.getDeclaredField(parameterMapping.getContent());
field.setAccessible(true);
Object value = field.get(param[0]);
//使用JDBC设置值
pstmt.setObject(i + 1, value);
}
ResultSet resultSet = pstmt.executeQuery();
//返回类型
Class<?> resultClass = Class.forName(statement.getResultType());
while (resultSet.next()) {
Object object = resultClass.newInstance();
//使用反射的方式为对象设置值并返回
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 0; i < metaData.getColumnCount(); i++) {
String columnName = metaData.getColumnName(i + 1);
Object value = resultSet.getObject(columnName);
PropertyDescriptor property = new PropertyDescriptor(columnName, resultClass);
Method writeMethod = property.getWriteMethod();
writeMethod.invoke(object, value);
}
result.add((E) object);
}
}finally {
close();
}
return result;
}
@Override
public int insert(Configuration configuration, MapperStatement statement, Object... param) throws Exception {
return doUpdate(configuration, statement, param);
}
@Override
public int doUpdate(Configuration configuration, MapperStatement statement, Object... param) throws Exception {
int num = 0;
try {
connection = configuration.getSource().getConnection();
String sql = statement.getSql();
//将XML中的sql转换为JDBC执行的sql
BoundSql boundSql = getBoundSql(sql);
//获取参数类型
String paramType = statement.getParamType();
Class<?> aClass = Class.forName(paramType);
String sqlText = boundSql.getSqlText();
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
PreparedStatement pstmt = connection.prepareStatement(sqlText);
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
Field field = aClass.getDeclaredField(parameterMapping.getContent());
field.setAccessible(true);
Object value = field.get(param[0]);
pstmt.setObject(i + 1, value);
}
num = pstmt.executeUpdate();
} finally {
close();
}
return num;
}
@Override
public int delete(Configuration configuration, MapperStatement statement, Object... param) throws Exception {
return doUpdate(configuration, statement, param);
}
@Override
public void close() throws SQLException {
connection.close();
}
/**
* 将sql中#{}转换为?号,并按照顺序设置到List<ParameterMapping>中,这样后面可以按照顺序从参数实体类中get出对应的值
* @param sql
* @return
*/
private BoundSql getBoundSql(String sql) {
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser =
new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
String parse = genericTokenParser.parse(sql);
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
return new BoundSql(parse, parameterMappings);
}
}
```
Mybatis学习笔记(二)-自定义一个简单版本的mybatis