MyBatis 是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
本文将详细介绍如何手写一个简化版的 MyBatis 框架,帮助读者深入理解 MyBatis 的工作原理和实现机制。
在开始手写 MyBatis 框架之前,我们需要了解 MyBatis 的核心组件及其功能:
Configuration
类是 MyBatis 的核心配置类,它包含了所有的配置信息,如数据源、Mapper 接口、SQL 语句等。
public class Configuration { private DataSource dataSource; private Map<String, MappedStatement> mappedStatements = new HashMap<>(); public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public MappedStatement getMappedStatement(String statementId) { return mappedStatements.get(statementId); } public void addMappedStatement(String statementId, MappedStatement mappedStatement) { this.mappedStatements.put(statementId, mappedStatement); } }
MappedStatement
类用于封装 SQL 语句、输入参数、输出结果等信息。
public class MappedStatement { private String id; private String sql; private Class<?> parameterType; private Class<?> resultType; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } public Class<?> getParameterType() { return parameterType; } public void setParameterType(Class<?> parameterType) { this.parameterType = parameterType; } public Class<?> getResultType() { return resultType; } public void setResultType(Class<?> resultType) { this.resultType = resultType; } }
SqlSessionFactory
是用于创建 SqlSession
的工厂类。
public class SqlSessionFactory { private Configuration configuration; public SqlSessionFactory(Configuration configuration) { this.configuration = configuration; } public SqlSession openSession() { return new DefaultSqlSession(configuration); } }
SqlSession
是 MyBatis 的核心接口,用于执行 SQL 命令、获取映射器和管理事务。
public interface SqlSession { <T> T selectOne(String statementId, Object parameter); <T> List<T> selectList(String statementId, Object parameter); <T> T getMapper(Class<T> type); }
DefaultSqlSession
是 SqlSession
的默认实现类。
public class DefaultSqlSession implements SqlSession { private Configuration configuration; private Executor executor; public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; this.executor = new SimpleExecutor(configuration); } @Override public <T> T selectOne(String statementId, Object parameter) { List<T> list = selectList(statementId, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new RuntimeException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } } @Override public <T> List<T> selectList(String statementId, Object parameter) { MappedStatement mappedStatement = configuration.getMappedStatement(statementId); return executor.query(mappedStatement, parameter); } @Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } }
Executor
是执行器接口,负责 SQL 语句的执行。
public interface Executor { <T> List<T> query(MappedStatement mappedStatement, Object parameter); }
SimpleExecutor
是 Executor
的简单实现类。
public class SimpleExecutor implements Executor { private Configuration configuration; public SimpleExecutor(Configuration configuration) { this.configuration = configuration; } @Override public <T> List<T> query(MappedStatement mappedStatement, Object parameter) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { connection = configuration.getDataSource().getConnection(); preparedStatement = connection.prepareStatement(mappedStatement.getSql()); if (parameter != null) { preparedStatement.setObject(1, parameter); } resultSet = preparedStatement.executeQuery(); return resultSetToObject(resultSet, mappedStatement.getResultType()); } catch (SQLException e) { throw new RuntimeException(e); } finally { try { if (resultSet != null) { resultSet.close(); } if (preparedStatement != null) { preparedStatement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } } private <T> List<T> resultSetToObject(ResultSet resultSet, Class<T> resultType) { List<T> result = new ArrayList<>(); try { while (resultSet.next()) { T obj = resultType.newInstance(); ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); for (int i = 1; i <= columnCount; i++) { String columnName = metaData.getColumnName(i); Object value = resultSet.getObject(columnName); Field field = resultType.getDeclaredField(columnName); field.setAccessible(true); field.set(obj, value); } result.add(obj); } } catch (Exception e) { throw new RuntimeException(e); } return result; } }
MapperProxy
是动态代理类,用于生成 Mapper 接口的代理对象。
public class MapperProxy<T> implements InvocationHandler { private SqlSession sqlSession; private Class<T> mapperInterface; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String statementId = mapperInterface.getName() + "." + method.getName(); MappedStatement mappedStatement = sqlSession.getConfiguration().getMappedStatement(statementId); if (mappedStatement == null) { throw new RuntimeException("Statement not found: " + statementId); } return sqlSession.selectOne(statementId, args[0]); } }
在 Configuration
类中添加 getMapper
方法,用于获取 Mapper 接口的代理对象。
public class Configuration { private DataSource dataSource; private Map<String, MappedStatement> mappedStatements = new HashMap<>(); public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public MappedStatement getMappedStatement(String statementId) { return mappedStatements.get(statementId); } public void addMappedStatement(String statementId, MappedStatement mappedStatement) { this.mappedStatements.put(statementId, mappedStatement); } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new MapperProxy<>(sqlSession, type)); } }
现在,我们已经完成了手写 MyBatis 框架的核心组件。接下来,我们可以编写一个简单的测试用例来验证框架的功能。
public class MyBatisTest { public static void main(String[] args) { // 创建数据源 DataSource dataSource = new SimpleDataSource("jdbc:mysql://localhost:3306/test", "root", "password"); // 创建 Configuration Configuration configuration = new Configuration(); configuration.setDataSource(dataSource); // 添加 MappedStatement MappedStatement mappedStatement = new MappedStatement(); mappedStatement.setId("com.example.UserMapper.selectUserById"); mappedStatement.setSql("SELECT * FROM user WHERE id = ?"); mappedStatement.setParameterType(Integer.class); mappedStatement.setResultType(User.class); configuration.addMappedStatement("com.example.UserMapper.selectUserById", mappedStatement); // 创建 SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactory(configuration); // 创建 SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 获取 Mapper 接口 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 执行查询 User user = userMapper.selectUserById(1); System.out.println(user); } }
public interface UserMapper { User selectUserById(Integer id); }
public class User { private Integer id; private String name; private Integer age; // getters and setters }
通过以上步骤,我们成功手写了一个简化版的 MyBatis 框架。虽然这个框架的功能远不及真正的 MyBatis 强大,但它已经具备了 MyBatis 的核心功能,如 SQL 映射、动态代理、结果集映射等。
通过手写 MyBatis 框架,我们不仅加深了对 MyBatis 工作原理的理解,还掌握了如何设计和实现一个简单的 ORM 框架。希望本文能对读者有所帮助,激发大家对框架设计和实现的兴趣。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。