手把手教会你如何自定义mini-mapper

发布于:2022-10-18 ⋅ 阅读:(445) ⋅ 点赞:(0)

 

本文向大家介绍对JDBC简单的封装,自定义mini-mapper,希望以此加深大家对此技术的理解。


一、JDBC简介

JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

Java 具有坚固、安全、易于使用、易于理解和可从网络上自动下载等特性,是编写数据库应用程序的杰出语言。所需要的只是 Java应用程序与各种不同数据库之间进行对话的方法。

数据库是实现持久化的一种途径,而JDBC则是通向数据库的桥梁。


二、环境准备

1、引入maven依赖

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>${mysql-connector.version}</version>
  <scope>runtime</scope>
</dependency>
<!-- 引入lombok,去除冗余代码 -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>${lombok.version}</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
 </dependency>

2、引入配置文件jdbc.properties

jdbc.driveClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mini_mapper?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&verifyServerCertificate=false&useSSL=false&useAffectedRows=true&zeroDateTimeBehavior=convertToNull
jdbc.username=root
jdbc.password=pf1234

3、新建测试数据表

CREATE TABLE `t_user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(255) DEFAULT '' COMMENT '姓名',
  `age` tinyint(3) unsigned DEFAULT '0' COMMENT '年龄',
  `birthday` date DEFAULT NULL COMMENT '生日',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


三、封装MiniJdbcTemplate

MiniJdbcTemplate提供对数据库表的增删改查操作

1、定义JdbcUtil

获取数据库链接,关闭数据库链接,放在JdbcUtil里维护

public final class JdbcUtil {
    
    /**
    * 数据库链接
    */
    private static String url = null;
    
    /**
    * 用户名
    */
    private static String username = null;
    
    /**
    * 密码
    */
    private static String password = null;
    
    static {
        try (
            InputStream inputStream = JdbcUtil.class.getClassLoader().getResourceAsStream("jdbc.properties")
        ) {
            Properties properties = new Properties();
            properties.load(inputStream);
            String driveClassName = properties.getProperty("jdbc.driveClassName");
            Class.forName(driveClassName);
            url = properties.getProperty("jdbc.url");
            username = properties.getProperty("jdbc.username");
            password = properties.getProperty("jdbc.password");
        } catch (Exception e) {
            throw new RuntimeException("加载驱动异常", e);
        }
    }
    
    private JdbcUtil() {
    }
    
    
    
    /**
    * 获取数据库链接
    */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }
    
    /**
    * 关闭链接
    */
    public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet) throws SQLException {
        if (resultSet != null) {
            resultSet.close();
        }
        
        if (preparedStatement != null) {
            preparedStatement.close();
        }
        
        if (connection != null) {
            connection.close();
        }
    }
}

2、定义常量

public interface Constant {

    String COMMA = ",";

    String UNDERSCORE = "_";

    char CHAR_UNDERSCORE = '_';

    String ASTERISK = "*";

    String SPACE = " ";

    String QUESTION_MARK = "?";

    String EQ = "=";

    String LEFT_BRACKET = "(";

    String RIGHT_BRACKET = ")";
}

3、定义数据转换策略接口

@FunctionalInterface
public interface RowMapper<T> {

    /**
     * 将结果集转为指定的Bean
     */
    T mapRow(ResultSet rs) throws SQLException;
}

4、基于策略查询集合

    /**
     * 基于策略查询集合
     */
    public static <T> List<T> queryForList(String sql, List<Object> params, RowMapper<T> rowMapper) throws SQLException {
        //获取链接
        Connection conn = JdbcUtil.getConnection();
        //获取执行平台
        PreparedStatement preparedStatement = getPreparedStatement(sql, params, conn);
        //获取结果集
        ResultSet rs = preparedStatement.executeQuery();
        List<T> list = new ArrayList<T>();
        while (rs.next()) {
            //将结果集转为指定的Bean
            T bean = rowMapper.mapRow(rs);
            list.add(bean);
        }
        //释放链接
        JdbcUtil.close(conn, preparedStatement, rs);
        return list;
    }

Junit测试

@Test
    public void testQueryRowMapper() throws SQLException {
        String sql = "select * from t_user where id = ?";
        List<Object> params = Collections.singletonList(1);
        List<User> list = MiniJdbcTemplate.queryForList(sql, params, rs -> {
            User user = new User();
            user.setId(rs.getInt("id"));
            user.setName(rs.getString("name"));
            user.setAge(rs.getInt("age"));
            user.setBirthday(rs.getDate("birthday"));
            return user;
        });
        System.out.println(list);
    }

结果: [User(id=1, name=张三, age=19, birthday=2003-05-02)]

5、基于反射查询集合

   /**
     * 基于反射查询集合
     */
    public static <T> List<T> queryForList(String sql, List<Object> params, Class<T> tClass) throws Exception {
        //获取链接
        Connection conn = JdbcUtil.getConnection();
        //获取执行平台
        PreparedStatement preparedStatement = getPreparedStatement(sql, params, conn);
        //获取结果集
        ResultSet rs = preparedStatement.executeQuery();
        List<T> list = new ArrayList<T>();
        while (rs.next()) {
            T bean = tClass.newInstance();
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            for (int i = 0; i < columnCount; i++) {
                //获取列名
                String columnName = metaData.getColumnName(i + 1);
                //获取属性名
                String fieldName = CommonUtil.getFieldName(columnName);
                Field field = tClass.getDeclaredField(fieldName);
                Object value;
                //mysql依赖包不支持传入date类型解析
                if (field.getType().equals(Date.class)) {
                    value = rs.getDate(columnName);
                } else {
                    value = rs.getObject(columnName, field.getType());
                }
                field.setAccessible(true);
                field.set(bean, value);
            }
            list.add(bean);
        }
        JdbcUtil.close(conn, preparedStatement, rs);
        return list;
    }

Junit测试

 @Test
    public void testQuery() throws Exception {
        String sql = "select * from t_user where id = ?";
        List<Object> params = Collections.singletonList(1);
        List<User> list = MiniJdbcTemplate.queryForList(sql, params, User.class);
        System.out.println(list);
    }

结果: [User(id=1, name=张三, age=19, birthday=2003-05-02)]

6、增删改方法

   /**
     * 增删改操作
     */
    public static int update(String sql, List<Object> params) throws SQLException {
        //获取链接
        Connection conn = JdbcUtil.getConnection();
        //获取执行平台
        PreparedStatement preparedStatement = getPreparedStatement(sql, params, conn);
        //执行更新(增删改)
        int row = preparedStatement.executeUpdate();
        //释放链接
        JdbcUtil.close(conn, preparedStatement, null);
        return row;
    }

Junit测试

@Test
    public void insert() throws SQLException {
        String sql = "insert into t_user(name,age,birthday) values(?,?,?)";
        List<Object> params = Arrays.asList("李四", 0, new Date());
        int row = MiniJdbcTemplate.update(sql, params);
        System.out.println(row);
    }

结果: 1


四、定义LambdaQueryWrapper

仿照maybatis-plus,通过Lambda表达式获取数据库字段,LambdaQueryWrapper职责是构建筛选条件

1、定义支持序列化的Function

@FunctionalInterface
public interface ConditionFunction<T, R> extends Function<T, R>, Serializable {
}

2、定义Lambda解析工具类

public final class LambdaUtils {

    private static final Pattern GET_PATTERN = Pattern.compile("^get[a-zA-Z].*");

    private LambdaUtils() {

    }

    /**
     * 获取字段名
     */
    public static <T> String fnToColumnName(ConditionFunction<T, ?> fn) {
        try {
            Method method = fn.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(fn);
            String methodName = serializedLambda.getImplMethodName();
            if (GET_PATTERN.matcher(methodName).find()) {
                methodName = methodName.substring(3);
            }
            return CommonUtil.getColumnName(methodName);
        } catch (Exception e) {
            throw new RuntimeException("获取属性名异常", e);
        }
    }
}

3、定义相关条件model

/**
 * 共享查询字段
 */
@Data
public class SharedString implements Serializable {

    private static final long serialVersionUID = -8841176360027140318L;

    private String stringValue;

}
/**
 * 查询的条件参数
 */
@Builder
@Data
public class SqlParam implements Serializable {

    private static final long serialVersionUID = 3693478483795808202L;
    
    /**
     * 数据库字段
     */
    private String columnName;
    
   /**
     * 属性值
     */
    private Object value;

    /**
     * 数据库关键字
     */
    private String sqlKeyword;

}

4、LambdaQueryWrapper

LambdaQueryWrapper目前就提供如下几种查询条件的构建,特别复杂的就不写了

public class LambdaQueryWrapper<T> {

    private static final String LIKE = "LIKE";

    private static final String NOT_LIKE = "NOT LIKE";

    private static final String EQ = "=";

    private static final String IN = "IN";

    private static final String NOT_IN = "NOT IN";

    private static final String GT = ">";

    private static final String LT = "<";

    /**
     * 查询字段
     */
    private SharedString sqlSelect = new SharedString();

    /**
     * 条件参数
     */
    private List<SqlParam> sqlParamList = new ArrayList<>();

    public LambdaQueryWrapper<T> eq(ConditionFunction<T, ?> fn, Object value) {
        addCondition(fn, value, EQ);
        return this;
    }

    public LambdaQueryWrapper<T> like(ConditionFunction<T, ?> fn, Object value) {
        addCondition(fn, value, LIKE);
        return this;
    }

    public LambdaQueryWrapper<T> notLike(ConditionFunction<T, ?> fn, Object value) {
        addCondition(fn, value, NOT_LIKE);
        return this;
    }

    public LambdaQueryWrapper<T> in(ConditionFunction<T, ?> fn, Object... values) {
        addCondition(fn, Arrays.asList(values), IN);
        return this;
    }

    public LambdaQueryWrapper<T> notIn(ConditionFunction<T, ?> fn, Object... values) {
        addCondition(fn, Arrays.asList(values), NOT_IN);
        return this;
    }

    public LambdaQueryWrapper<T> gt(ConditionFunction<T, ?> fn, Object value) {
        addCondition(fn, value, GT);
        return this;
    }

    public LambdaQueryWrapper<T> lt(ConditionFunction<T, ?> fn, Object value) {
        addCondition(fn, value, LT);
        return this;
    }

    public LambdaQueryWrapper<T> select(ConditionFunction<T, ?>... fns) {
        if (!CommonUtil.isEmpty(fns)) {
            String stringValue = Arrays.stream(fns).map(LambdaUtils::fnToColumnName).collect(Collectors.joining(Constant.COMMA));
            sqlSelect.setStringValue(stringValue);
        }
        return this;
    }

    /**
     * 添加条件
     */
    private void addCondition(ConditionFunction<T, ?> fn, Object value, String sqlKeyword) {
        SqlParam sqlParam = SqlParam.builder()
                .columnName(LambdaUtils.fnToColumnName(fn))
                .value(value)
                .sqlKeyword(sqlKeyword)
                .build();
        sqlParamList.add(sqlParam);
    }

    public String getSqlSelect() {
        return CommonUtil.isBank(sqlSelect.getStringValue()) ? Constant.ASTERISK : sqlSelect.getStringValue();
    }

    public List<SqlParam> getSqlParamList() {
        return sqlParamList;
    }
}


五、定义AbstractBaseMapper

有了上面的铺垫,最后就是组装sql了,AbstractBaseMapper职责是通过LambdaQueryWrapper来组装sql和参数列表,传递给MiniJdbcTemplate

1.定义TableName

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableName {
    String value();
}

2、AbstractBaseMapper

public abstract class AbstractBaseMapper<T> {

    private static final String INSERT_SQL = "INSERT INTO %s(%s) values(%s)";

    private static final String UPDATE_SQL = "UPDATE %s SET ";

    private static final String QUERY_SQL = "SELECT %s FROM %s";

    private static final String AND = "AND";

    private static final String WHERE = "WHERE";

    private Class<T> beanClass;

    private String tableName;

    public AbstractBaseMapper() {
        //获取泛型的class
        beanClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        //获取表名
        tableName = beanClass.getAnnotation(TableName.class).value();
    }

    /**
     * 新增
     */
    public int insertSelective(T bean) throws IllegalAccessException, SQLException {
        List<Object> params = new ArrayList<>();
        List<String> columns = new ArrayList<>();
        List<String> values = new ArrayList<>();
        Field[] fields = beanClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(Boolean.TRUE);
            Object value = field.get(bean);
            if (value == null) {
                continue;
            }
            params.add(value);
            columns.add(CommonUtil.getColumnName(field.getName()));
            values.add(Constant.QUESTION_MARK);
        }
        String sql = String.format(INSERT_SQL, tableName, String.join(Constant.COMMA, columns), String.join(Constant.COMMA, values));
        return MiniJdbcTemplate.update(sql, params);
    }

    /**
     * 修改
     */
    public int updateSelective(T bean, LambdaQueryWrapper<T> wrapper) throws IllegalAccessException, SQLException {
        List<Object> params = new ArrayList<>();
        List<String> setParams = new ArrayList<>();
        Field[] fields = beanClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(Boolean.TRUE);
            Object value = field.get(bean);
            if (value == null) {
                continue;
            }
            params.add(value);
            setParams.add(CommonUtil.getColumnName(field.getName()) + Constant.EQ + Constant.QUESTION_MARK);
        }
        StringBuilder condition = new StringBuilder();
        handleSqlParam(wrapper, condition, params);
        String sql = String.format(UPDATE_SQL, tableName) + String.join(Constant.COMMA, setParams) +
                condition.toString();
        return MiniJdbcTemplate.update(sql, params);
    }

    /**
     * 查询
     */
    public List<T> list(LambdaQueryWrapper<T> wrapper) throws Exception {
        List<Object> params = new ArrayList<>();
        StringBuilder condition = new StringBuilder();
        handleSqlParam(wrapper, condition, params);
        String sql = String.format(QUERY_SQL, wrapper.getSqlSelect(), tableName) + condition.toString();
        return MiniJdbcTemplate.queryForList(sql, params, beanClass);
    }

    /**
     * 处理条件
     */
    private void handleSqlParam(LambdaQueryWrapper<T> wrapper, StringBuilder condition, List<Object> params) {
        List<SqlParam> sqlParamList = wrapper.getSqlParamList();
        if (CommonUtil.isNotEmpty(sqlParamList)) {
            condition.append(Constant.SPACE).append(WHERE).append(Constant.SPACE);
            for (int i = 0; i < sqlParamList.size(); i++) {
                SqlParam sqlParam = sqlParamList.get(i);
                if (i > 0) {
                    condition.append(Constant.SPACE).append(AND).append(Constant.SPACE);
                }
                StringBuilder value = new StringBuilder();
                Object paramValue = sqlParam.getValue();
                if (paramValue instanceof List) {
                    value.append(Constant.LEFT_BRACKET);
                    List<Object> valueList = (List<Object>) paramValue;
                    for (int j = 0; j < valueList.size(); j++) {
                        Object obj = valueList.get(j);
                        params.add(obj);
                        if (j > 0) {
                            value.append(Constant.COMMA);
                        }
                        value.append(Constant.QUESTION_MARK);
                    }
                    value.append(Constant.RIGHT_BRACKET);
                } else {
                    value.append(Constant.QUESTION_MARK);
                    params.add(paramValue);
                }
                condition.append(sqlParam.getColumnName()).append(Constant.SPACE).append(sqlParam.getSqlKeyword()).append(Constant.SPACE).append(value.toString());
            }
        }
    }
}

3、Junit测试

@TableName("t_user")
@Data
public class User {

    /**
     * 主键
     */
    private Integer id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 生日
     */
    private Date birthday;
}
public class UserMapper extends AbstractBaseMapper<User> {
}

测试insertSelective

 @Test
    public void insertTest() throws SQLException, IllegalAccessException {
        UserMapper userMapper = new UserMapper();
        User user = new User();
        user.setName("王五");
        user.setAge(0);
        user.setBirthday(new Date());
        int row = userMapper.insertSelective(user);
        System.out.println(row);
    }

结果: 1

测试updateSelective

 @Test
    public void updateTest() throws SQLException, IllegalAccessException {
        UserMapper userMapper = new UserMapper();
        User user = new User();
        user.setAge(12);
        int row = userMapper.updateSelective(user, new LambdaQueryWrapper<User>().eq(User::getId, 3));
        System.out.println(row);
    }

结果: 1

测试查询

@Test
    public void queryTest() throws Exception {
        UserMapper userMapper = new UserMapper();
        List<User> userList = userMapper.list(new LambdaQueryWrapper<User>()
                .select(User::getId, User::getName, User::getAge)
                .in(User::getId, 3, 4)
                .gt(User::getAge,1)
                .eq(User::getName, "王五"));
        System.out.println(userList);
    }

结果: [User(id=3, name=王五, age=12, birthday=null)]


六、总结

大家在实际开发中还是使用业界成熟的框架,不要自己手动实现,mini-mapper只是实现了一些最基本的功能,代码实现还存在瑕疵,目的是加深技术的理解,起一个抛砖引玉的作用。

本文含有隐藏内容,请 开通VIP 后查看

微信公众号

今日签到

点亮在社区的每一天
去签到