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

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



一、JDBC简介

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

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

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

二、环境准备

1、引入maven依赖

  mysql

  mysql-connector-java

  ${mysql-connector.version}

  runtime

  org.projectlombok

  lombok

  ${lombok.version}

  provided

  junit

  junit

  4.12

  test

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 {

    /**

    * 将结果集转为指定的Bean

    */

    T mapRow(ResultSet rs) throws SQLException;

}

4、基于策略查询集合

    /**

    * 基于策略查询集合

    */

    public static List queryForList(String sql, List params, RowMapper rowMapper) throws SQLException {

        //获取链接

        Connection conn = JdbcUtil.getConnection();

        //获取执行平台

        PreparedStatement preparedStatement = getPreparedStatement(sql, params, conn);

        //获取结果集

        ResultSet rs = preparedStatement.executeQuery();

        List list = new ArrayList();

        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 params = Collections.singletonList(1);

        List 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 List queryForList(String sql, List params, Class tClass) throws Exception {

        //获取链接

        Connection conn = JdbcUtil.getConnection();

        //获取执行平台

        PreparedStatement preparedStatement = getPreparedStatement(sql, params, conn);

        //获取结果集

        ResultSet rs = preparedStatement.executeQuery();

        List list = new ArrayList();

        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 params = Collections.singletonList(1);

        List 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 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 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 extends Function, Serializable {

}

2、定义Lambda解析工具类

public final class LambdaUtils {

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

    private LambdaUtils() {

    }

    /**

    * 获取字段名

    */

    public static String fnToColumnName(ConditionFunction 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 {

    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 sqlParamList = new ArrayList<>();

    public LambdaQueryWrapper eq(ConditionFunction fn, Object value) {

        addCondition(fn, value, EQ);

        return this;

    }

    public LambdaQueryWrapper like(ConditionFunction fn, Object value) {

        addCondition(fn, value, LIKE);

        return this;

    }

    public LambdaQueryWrapper notLike(ConditionFunction fn, Object value) {

        addCondition(fn, value, NOT_LIKE);

        return this;

    }

    public LambdaQueryWrapper in(ConditionFunction fn, Object... values) {

        addCondition(fn, Arrays.asList(values), IN);

        return this;

    }

    public LambdaQueryWrapper notIn(ConditionFunction fn, Object... values) {

        addCondition(fn, Arrays.asList(values), NOT_IN);

        return this;

    }

    public LambdaQueryWrapper gt(ConditionFunction fn, Object value) {

        addCondition(fn, value, GT);

        return this;

    }

    public LambdaQueryWrapper lt(ConditionFunction fn, Object value) {

        addCondition(fn, value, LT);

        return this;

    }

    public LambdaQueryWrapper select(ConditionFunction... 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 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 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 {

    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 beanClass;

    private String tableName;

    public AbstractBaseMapper() {

        //获取泛型的class

        beanClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

        //获取表名

        tableName = beanClass.getAnnotation(TableName.class).value();

    }

    /**

    * 新增

    */

    public int insertSelective(T bean) throws IllegalAccessException, SQLException {

        List params = new ArrayList<>();

        List columns = new ArrayList<>();

        List 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 wrapper) throws IllegalAccessException, SQLException {

        List params = new ArrayList<>();

        List 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 list(LambdaQueryWrapper wrapper) throws Exception {

        List 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 wrapper, StringBuilder condition, List params) {

        List 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 valueList = (List) 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 {

}

测试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().eq(User::getId, 3));

        System.out.println(row);

    }

结果: 1

测试查询

@Test

    public void queryTest() throws Exception {

        UserMapper userMapper = new UserMapper();

        List userList = userMapper.list(new LambdaQueryWrapper()

                .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只是实现了一些最基本的功能,代码实现还存在瑕疵,目的是加深技术的理解,起一个抛砖引玉的作用。

你可能感兴趣的:(手把手教会你如何自定义mini-mapper)