手写 MyBatis
1. 前言
本篇博客,将使用 JDK 动态代理、注解、反射等技术,编写一个最简单的 MyBatis,可基本实现对象的增删查改
2. 注解的定义
2.1 Delete 注解
/** * @ClassName Delete * @Descriiption 删除注解 * @Author yanjiantao * @Date 2019/6/27 11:03 **/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Delete { public String value(); }
2.2 Insert 注解
/** * @ClassName Delete * @Descriiption 保存注解 * @Author yanjiantao * @Date 2019/6/27 11:03 **/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Insert { public String value(); }
2.3 Select 注解
/** * @ClassName Delete * @Descriiption 查询注解 * @Author yanjiantao * @Date 2019/6/27 11:03 **/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Select { public String value(); }
2.4 Update 注解
/** * @ClassName Delete * @Descriiption 更新注解 * @Author yanjiantao * @Date 2019/6/27 11:03 **/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Update { public String value(); }
3. jdk 动态代理
3.1 方法代理类
/** * @ClassName MethodProxy * @Descriiption 方法代理 * @Author yanjiantao * @Date 2019/6/27 11:11 **/ public class MethodProxy implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return DaoOperatorHandler.handle(method,args); } }
该类实现 JDK 的 InvocationHandler
方法,并且实验 invoke
方法,即可实现 JDK 的动态代理
3.2 动态代理工厂类
/** * @ClassName MethodProxyFactory * @Descriiption 代理工厂类 * @Author yanjiantao * @Date 2019/6/28 15:40 **/ public class MethodProxyFactory { @SuppressWarnings("unchecked") public static <T> T getBean(Class<T> clazz) { final MethodProxy methodProxy = new MethodProxy(); return (T) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[]{clazz}, methodProxy ); } }
该工厂的方法主要是得到 Mapper
的实例,并且把 Mapper
交给 JDK 进行动态代理
4. 数据库操作
4.1 数据库操作处理类
/** * @ClassName DaoOperatorHandler * @Descriiption 数据库操作处理器 * @Author yanjiantao * @Date 2019/6/27 11:39 **/ public class DaoOperatorHandler { public static Object handle(Method method, Object[] parameters) throws SQLException, ClassNotFoundException { String sql = null; // 插入 if (method.isAnnotationPresent(Insert.class)) { sql = checkSql(method.getAnnotation(Insert.class).value(), Insert.class.getSimpleName()); insert(sql, parameters); // 更新 }else if (method.isAnnotationPresent(Update.class)) { sql = checkSql(method.getAnnotation(Update.class).value(), Update.class.getSimpleName()); return update(sql, parameters); // 查询 }else if (method.isAnnotationPresent(Select.class)) { sql = checkSql(method.getAnnotation(Select.class).value(), Select.class.getSimpleName()); Class returnType = method.getReturnType(); if (List.class.isAssignableFrom(returnType)) { return selectMany(sql, parameters); }else { return selectMany(sql, parameters).get(0); } }else if (method.isAnnotationPresent(Delete.class)) { sql = checkSql(method.getAnnotation(Delete.class).value(), Delete.class.getSimpleName()); return update(sql, parameters); } System.out.println(sql); return null; } /** * 插入 * @param sql sql * @param parameters 参数 * @throws SQLException SQLException * @throws ClassNotFoundException ClassNotFoundException */ private static void insert(String sql, Object[] parameters) throws SQLException, ClassNotFoundException { Connection connection = JDBCUtils.getConnection(); PreparedStatement statement = connection.prepareStatement(sql); for (int i = 0; i < parameters.length; i++) { statement.setObject(i+1, (String) parameters[i]); } statement.execute(); connection.close(); } /** * 插入 * @param sql sql * @param parameters 参数 * @throws SQLException SQLException * @throws ClassNotFoundException ClassNotFoundException */ private static Integer update(String sql, Object[] parameters) throws SQLException, ClassNotFoundException { Connection connection = JDBCUtils.getConnection(); PreparedStatement statement = connection.prepareStatement(sql); for (int i = 0; i < parameters.length; i++) { statement.setObject(i+1, parameters[i]); } int result = statement.executeUpdate(); connection.close(); return result; } /** * 插入 * @param sql sql * @param parameters 参数 * @return List<T> * @throws SQLException SQLException * @throws ClassNotFoundException ClassNotFoundException */ private static <T> List<T> selectMany(String sql, Object[] parameters) throws SQLException, ClassNotFoundException { Connection connection = JDBCUtils.getConnection(); PreparedStatement statement = connection.prepareStatement(sql); for (int i = 0; parameters != null && i < parameters.length; i++) { statement.setObject(i+1, parameters[i]); } ResultSet resultSet = statement.executeQuery(); List<T> result = new ResultToMapper<T>().mapToObject(resultSet,User.class); return result; } /** * 检查sql * @param sql sql * @param type type * @return the sql * @throws SQLException SQLException */ private static String checkSql(String sql, String type) throws SQLException { String sqlType = sql.split(" ")[0]; if (!sqlType.equalsIgnoreCase(type)) { throw new SQLException("SQL语句错误"); } return sql; } }
该类主要是根据被代理类是否包含相关注解,根据注解的类型,进行增删查改的操作,最后,再将增删查改后的处理结果,使用反射映射到实体类上
5 实体类
5.1 用户实体类
/** * @ClassName User * @Descriiption 用户实体类 * @Author yanjiantao * @Date 2019/6/28 15:24 **/ @Data public class User { private Integer id; private String username; private String password; }
6 工具类
6.1 JDBCUtils
/** * @ClassName JDBCUtils * @Descriiption jdbc连接工具类 * @Author yanjiantao * @Date 2019/6/28 16:24 **/ public class JDBCUtils { public static Connection getConnection() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.cj.jdbc.Driver"); String username = "root"; String password = "root123456"; return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&serverTimezone=GMT%2B8", username, password); } }
6.2 ResultToMapper
/** * @ClassName ResultToMapper * @Descriiption mysql查询结果转换为实体bean * @Author yanjiantao * @Date 2019/6/28 17:35 **/ public class ResultToMapper<T> { public List<T> mapToObject(ResultSet resultSet, Class<?> clazz) { if (resultSet == null) { return null; } List<T> result = null; try { while (resultSet.next()) { T bean = (T) clazz.newInstance(); ResultSetMetaData metaData = resultSet.getMetaData(); for (int i = 0; i < metaData.getColumnCount(); i++) { String columnName = metaData.getColumnName(i + 1); Object columnValue = resultSet.getObject(i + 1); Field field = clazz.getDeclaredField(columnName); if (field != null && columnValue != null) { field.setAccessible(true); field.set(bean,columnValue); } } if (result == null) { result = new ArrayList<>(); } result.add(bean); } } catch (SQLException | InstantiationException | IllegalAccessException | NoSuchFieldException e) { e.printStackTrace(); } if (result == null) { return Collections.emptyList(); } return result; } }
该类主要是将 mysql 查询的结果,通过反射,映射到实体类上
7 Mapper
public interface UserMapper { @Insert("insert into user (username,password) values (?,?)") public void addUser(String name, String password); @Select("select * from user") public List<User> findUsers(); @Select("select * from user where id = ?") public User getUser(Integer id); @Update("update user set username = ? , password=? where id=?") public Integer updateUser(String name, String password, Integer id); @Delete("delete from user where id=?") public Integer deleteUser(Integer id); }
8 测试类
@Slf4j public class UserMapperTest { @Test public void addUser() { UserMapper userMapper = MethodProxyFactory.getBean(UserMapper.class); userMapper.addUser("boolean-","123456"); log.info("---------->"); } @Test public void findUsers() { UserMapper userMapper = MethodProxyFactory.getBean(UserMapper.class); List<User> list = userMapper.findUsers(); log.info("---------->list={}", list); } @Test public void getUser() { UserMapper userMapper = MethodProxyFactory.getBean(UserMapper.class); User user = userMapper.getUser(2); log.info("---------->user={}", user); } @Test public void updateUser() { UserMapper userMapper = MethodProxyFactory.getBean(UserMapper.class); Integer result = userMapper.updateUser("鄢剑涛update", "yjt123", 1); log.info("count={}", result); } @Test public void deleteUser() { UserMapper userMapper = MethodProxyFactory.getBean(UserMapper.class); Integer count = userMapper.deleteUser(1); log.info("count={}", count); } }
9 总结
这次的编写简单的 mybatis,让我对 java 基础有了进一步的了解,明白了反射、注解的厉害之处,也了解了 JDK 动态代理设计模式,总之,收获很大!!
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于