手寫MyBatis框架
阿新 • • 發佈:2022-03-26
手寫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=趙六)]