手写MyBatis框架
手写Mybatis框架更有利于了解框架底层,做到知其然知其所以然。
原生JDBC
/**
* 原生jdbc写法
*
* @author lipangpang
* @date 2022/3/13
* @description
*/
public class JdbcDemo {
public static void main(String[] args) {
MallUser user = new MallUser();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai", "root", "123456");
String sql = "select * from mall_user where name = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "张三");
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
Long id = resultSet.getLong("id");
String username = resultSet.getString("name");
user.setId(id);
user.setName(username);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException sqlException) {
sqlException.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException sqlException) {
sqlException.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException sqlException) {
sqlException.printStackTrace();
}
}
}
System.out.println(user);
}
}
原生JDBC写法的弊端有:
-
数据库配置信息及sql语句硬编码。
-
重复创建和关闭数据库连接。
框架写法
此处文件的命名参考MyBatis源码命名风格,有利于后续查看源码。
工程结构如下:
表结构如下:
框架大致流程如下:
-
创建mapper.xml文件用来存储sql查询语句,创建sqlMapConfig.xml来存储数据库配置信息和mapper.xml文件所在路径。
sqlMapConfig.xml
<configuration>
<dataSource>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</dataSource>
<mapper resource="MallUserMapper.xml"></mapper>
<mapper resource="MallRoleMapper.xml"></mapper>
</configuration>
MallUserMapper.xml
<mapper namespace="org.example.dao.IMallUserDao">
<select id="queryAll" parameterType="org.example.entity.MallUser" resultType="org.example.entity.MallUser">
select * from mall_user
</select>
<select id="selectOne" parameterType="org.example.entity.MallUser" resultType="org.example.entity.MallUser">
select * from mall_user where id = #{id} and name = #{name}
</select>
<insert id="insertOne" parameterType="org.example.entity.MallUser" resultType="int">
insert into mall_user(name) values(#{name})
</insert>
</mapper>
MallRoleMapper.xml
<mapper namespace="org.example.dao.IMallRoleDao">
<select id="queryAll" parameterType="org.example.entity.MallRole" resultType="org.example.entity.MallRole">
select * from mall_role
</select>
<select id="selectOne" parameterType="org.example.entity.MallRole" resultType="org.example.entity.MallRole">
select * from mall_role where id = #{id} and name = #{name}
</select>
<insert id="insertOne" parameterType="org.example.entity.MallRole" resultType="int">
insert into mall_role(name) values(#{name})
</insert>
</mapper> -
按照面向对象编程思想,将第一步解析出来的内容分别封装到MappedStatement和Configuration实体类中。
/**
* 配置文件对象
*
* @author lipangpang
* @date 2022/3/13
* @description
*/
@Data
public class Configuration {
private DataSource dataSource;
//key:命名空间+id
private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
}
/**
* mapper配置文件对象
*
* @author lipangpang
* @date 2022/3/13
* @description
*/
@Data
public class MappedStatement {
/**
* 标识
*/
private String id;
/**
* 返回结果类型
*/
private String resultType;
/**
* 参数查询类型
*/
private String parameterType;
/**
* sql语句
*/
private String sql;
} -
通过SqlSessionFactoryBuilder构建者创建SqlSessionFactory。
/**
* SqlSessionFactory构建者
*
* @author lipangpang
* @date 2022/3/13
* @description
*/
public class SqlSessionFactoryBuilder {
/**
* 构建
*
* @param inputStream 文件配置流
* @return
* @throws PropertyVetoException
* @throws DocumentException
*/
public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {
//解析配置文件,封装到到Configuration
Configuration configuration = new XMLConfigBuilder().parseConfig(inputStream);
//创建sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return sqlSessionFactory;
}
} -
通过sqlSessionFactory工厂创建SqlSession。
/**
* 默认SqlSessionFactory工厂
*
* @author lipangpang
* @date 2022/3/13
* @description
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(this.configuration);
}
} -
通过SqlSession执行sql语句(此处包含动态代理的执行方法)。
/**
* 默认SqlSession
*
* @author lipangpang
* @date 2022/3/13
* @description
*/
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementId, Object... params) throws Exception {
Executor executor = new SimpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
List<Object> objects = executor.query(configuration, mappedStatement, params);
return (List<E>) objects;
}
@Override
public <T> T selectOne(String statementId, Object... params) throws Exception {
List<Object> objects = this.selectList(statementId, params);
if (objects != null && objects.size() == 1) {
return (T) objects.get(0);
} else {
throw new RuntimeException("返回结果为空或返回结果过多");
}
}
@Override
public Integer insertOne(String statementId, Object... params) throws Exception {
Executor executor = new SimpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return executor.save(configuration, mappedStatement, params);
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
//通过jdk动态代理获取对象
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new
InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法名(这也是为什么xml中id要和接口方法名一致)
String methodName = method.getName();
//类全路径名(这也是为什么xml中namespace要和接口全路径一致)
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
return selectList(statementId, args);
}
if (genericReturnType.getTypeName().contains("Integer")) {
return insertOne(statementId, args);
}
return selectOne(statementId, args);
}
});
return (T) proxyInstance;
}
}
测试
测试代码:
/**
* MyBatis测试类
*
* @author lipangpang
* @date 2022/3/13
* @description
*/
public class MybatisTest {
public static void main(String[] args) throws Exception {
//读取配置文件为流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//将配置文件封装成对象并创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
MallUser user = new MallUser();
user.setId(1L);
user.setName("张三");
MallUser userNew = new MallUser();
userNew.setName("赵六");
//查询单个对象
MallUser mallUserDb = sqlSession.selectOne("org.example.dao.IMallUserDao.selectOne", user);
System.out.println("查询单个对象:" + mallUserDb);
//新增单个对象
Integer insertSuccessNumber = sqlSession.insertOne("org.example.dao.IMallUserDao.insertOne", userNew);
System.out.println("新增单个对象:" + insertSuccessNumber);
//查询所有对象
List<MallUser> mallUsers = sqlSession.selectList("org.example.dao.IMallUserDao.queryAll");
System.out.println("查询所有对象:" + mallUsers);
IMallUserDao mallUserDao = sqlSession.getMapper(IMallUserDao.class);
//通过代理查询所有对象
List<MallUser> mallUsersByProxy = mallUserDao.queryAll();
System.out.println("通过代理查询所有对象:" + mallUsersByProxy);
}
}
返回结果:
查询单个对象:MallUser(id=1, name=张三)
新增单个对象:1
查询所有对象:[MallUser(id=1, name=张三), MallUser(id=2, name=李四), MallUser(id=3, name=王五), MallUser(id=4, name=赵六)]
通过代理查询所有对象:[MallUser(id=1, name=张三), MallUser(id=2, name=李四), MallUser(id=3, name=王五), MallUser(id=4, name=赵六)]