手写 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 动态代理设计模式,总之,收获很大!!
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于