JavaEE练习项目--快递e栈(第三天)

JavaEE练习项目–快递e栈(第三天)

内容地址
需求分析链接地址
建表,工具类书写链接地址
Jdbc工具类链接地址
三层架构,阿里云短信链接地址
前端Ajax链接地址

我们的项目使用原生的JDBC+Servlet来就进行开发,对于我们的Servlet,我们已经仿照了springMVC的做法来进行优化,我们就可以集中注意力在业务的开发上了!

连接池

DruidUtil

简单的调用了Druid连接池,将获取连接和关闭的方法都进行了封装。

public class DruidUtil {

    /**
     * 数据源
     */
    private static DataSource dataSource;
    static{
        try {
            Properties ppt = new Properties();
            InputStream stream = DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");
            ppt.load(stream);
            dataSource = DruidDataSourceFactory.createDataSource(ppt);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据源
     *
     * @return {@link DataSource}
     */
    public static DataSource getDataSource() {
        return dataSource;
    }

    /**
     * 获取连接
     * 从连接池中取出一个连接给用户
     *
     * @return {@link Connection}
     */
    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }


    /**
     * 关闭
     *
     * @param conn  康涅狄格州
     * @param state 状态
     * @param rs    rs
     */
    public static void close(Connection conn, Statement state, ResultSet rs){
        try {
            if(rs!=null) {
                rs.close();
            }
        } catch (Exception throwables) {
            throwables.printStackTrace();
        }
        try {
            if(state!=null) {
                state.close();
            }
        } catch (Exception throwables) {
            throwables.printStackTrace();
        }
        try {
            if(conn!=null) {
                conn.close();
            }
        } catch (Exception throwables) {
            throwables.printStackTrace();
        }
    }
}

配置文件

url=jdbc:mysql:///express?useUnicode=true&characterEncoding=utf-8
username=root
password=
driverClassName=com.mysql.jdbc.Driver
initialSize=5
maxActive=10
minIdle=5
maxWait=3000

Jdbc工具

对于我们的原生的JDBC我们也需要进行优化

原来的代码:

我们已将将连接的获取和关闭方法封装给了DruiUtil这个工具类,但是我们还是免不了重复的代码:

  • 每个方法都有获取连接和关闭连接的方法
  • 每个方法都要预编译SQL,执行查询方法,然后结果集封装为对象
  • 每个方法还会要进行异常处理
 @Override
    public List<Map<String, Integer>> console() {
        ArrayList<Map<String,Integer>> data = new ArrayList<>();
        //1.    获取数据库的连接
        Connection conn = DruidUtil.getConnection();
        PreparedStatement state = null;
        ResultSet result = null;
        //2.    预编译SQL语句
        try {
            state = conn.prepareStatement(SQL_CONSOLE);
            //3.    填充参数(可选)
            //4.    执行SQL语句
            result = state.executeQuery();
            //5.    获取执行的结果
            if(result.next()){
                int data1_size = result.getInt("data1_size");
                int data1_day = result.getInt("data1_day");
                int data2_size = result.getInt("data2_size");
                int data2_day = result.getInt("data2_day");
                Map data1 = new HashMap();
                data1.put("data1_size",data1_size);
                data1.put("data1_day",data1_day);
                Map data2 = new HashMap();
                data2.put("data2_size",data2_size);
                data2.put("data2_day",data2_day);
                data.add(data1);
                data.add(data2);
            }
            //6.    资源的释放
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            DruidUtil.close(conn,state,result);
        }

        return data;
    }

    /**
     * 用于查询所有快递
     *
     * @param limit      是否分页的标记,true表示分页。false表示查询所有快递
     * @param offset     SQL语句的起始索引
     * @param pageNumber 页查询的数量
     * @return 快递的集合
     */
    @Override
    public List<Express> findAll(boolean limit, int offset, int pageNumber) {
        ArrayList<Express> data = new ArrayList<>();
        //1.    获取数据库的连接
        Connection conn = DruidUtil.getConnection();
        PreparedStatement state = null;
        ResultSet result = null;
        //2.    预编译SQL语句
        try {
            if(limit) {
                state = conn.prepareStatement(SQL_FIND_LIMIT);
                //3.    填充参数(可选)
                state.setInt(1,offset);
                state.setInt(2,pageNumber);
            }else {
                state = conn.prepareStatement(SQL_FIND_ALL);
            }

            //4.    执行SQL语句
            result = state.executeQuery();
            //5.    获取执行的结果
            while(result.next()){
                int id = result.getInt("id");
                String number = result.getString("number");
                String username = result.getString("username");
                String userPhone = result.getString("userPhone");
                String company = result.getString("company");
                String code = result.getString("code");
                Timestamp inTime = result.getTimestamp("inTime");
                Timestamp outTime = result.getTimestamp("outTime");
                int status = result.getInt("status");
                String sysPhone = result.getString("sysPhone");
                Express e = new Express(id,number,username,userPhone,company,code,inTime,outTime,status,sysPhone);
                data.add(e);
            }
            //6.    资源的释放
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            DruidUtil.close(conn,state,result);
        }
        return data;
    }

解决方案

  • 首先我们的JDBC可以分为两种方法:增删改方法,查询方法。其中增删改方法返回值都是int,即影响的行数,而查询方法的结果有多种:返回一个int,返回一个对象,返回对象列表List,返回一个Map。所以这是优化的难点。

  • 我们的JDBC核心部分就是SQL,返回值类型和参数了。一个给定的SQL和参数,我们就可以直接在数据库中获取结果了,然后如果我们给定了返回值类型,比如规定返回值类型是一个Student对象,那么可以将结果集封装到这个对象中去。

  • 我们的数据库类型和Java类型是不匹配的,比如varchar和char这些,一般我们认为和String是对应的,所以我们这里也需要进行配置。

方法设计

  1. 对于增删改查方法,就是传入SQL,和参数,由于参数的数量不确定可以使用不定量参数来解决。

    示例:

    • int update(String sql,Object ... objects);
  2. 由于查询方法种类比较多,使用一个方法涵盖万千是比较困难的,我们根据返回值的结果来划分:

    • Map<String,T> queryForMap(String sql,Class<T> className,Object ... objects)
      将结果封装为Map,我们的Map的key是参数名,参数值可以是任何类型,包括对象,所以我们需要传入一个Class对象,用户限定返回值类型,如果我们的查询语句有多种返回结果,那么就是用Object
    • List queryForList(String sql,Class<T> className,Object ... objects)
      将查询结果封装为List,和之前的map方法一样,也要传入一个Class对象
    • Object queryForObject(String sql,Class<T> className,Object ... objects)
      • 将查询结果封装为Object,和之前的map方法一样,也要传入一个Class对象
  3. 我们传入一个Class目的是通过反射机制创建对象和调用方法。这里的对象简单分为两类:POJO和基本数据类型包装类,我们通过一些条件判断来区分,然后使用不用的方式将数据库的查询结果封装进这些对象中。

    • POJO对象

      每一个属性都有对应的get和set方法

    • java.lang包对象

      也就是基本数据类型的包装类,这些对象没有get和set方法,但是我们可以直接构造方法传入对象来实现。

  4. 对于我们数据库查询出来的结果集ResultSet,JDBC提供了丰富的Api,包括查询结果的字段数,各字段的名字,类型等各种结果,我们可以通过这些方法来将数据库查询结果和我们要封装的对象的属性一一对应起来:

    • ResultSetMetaData metaData = resultSet.getMetaData()方法可以获取结果的详细数据。

      我们先得到字段数,然后遍历每一个字段,通过switch()这种暴力的方式将每个字段的类型和Java的数据类型一一对应起来,然后使用get方法来获取结果,然后封装进对象属性中:

      示例:

      • 比如我们要封装为一个String的对象,我们数据库查询结果为字段名为username,值为“dulao”。我们先获取字段的值,比如varchar,然后使用switch一一对应,结果是String.class。然后我们根据swith的结果,调用resultSet的“get”+“S”+"tring"拼接的方式来找到对应的获取String类型的方法,然后使用String对象的newInstance(resultSet.getString(1))的方式来填充我们的Stirng对象
      • 对于POJO对象(Student),在switch之后,我们需要获得字段名username,然后拼接字符串"set"+“U”+"sername"的方式找到setUsername这个方法,然后将查询结果封装进Student的username属性,然后循环遍历下一个属性,最后完成对Student对象每个属性的填充。
      • 我们看到对于基本包装数据类型,处理Integer的有一定的区别以外,其他的都是get加上数据类型首字母大写其他字母小写的方式,然后参数都是int(字段数),这个可以用for循环轻松填充。
      • image-20201003152547641
      • 这是我写的switch,对于现在的项目,只涉及这些类型,其实这个匹配表可单独抽取出一个配置文件,根据不同的需求进行不同配置。
      • image-20201003152829021
      • 还有就是,我们填充POJO对象的时候,都是使用set加上查询结果字段名的首字母大写其他字母小写的方式来实现的,虽然非常有效。但是拓展性比较差,这部分也可以抽取出来,我们的queryForObject方法传参的时候只传入了Class对象,这里可以优化一下,比如我们定义一个接口然后我们自定义实现类来做一一对应关系。
      • image-20201003152931582

参考代码

Jdbc代码优化到一行了!这样我们只要给定SQL,参数,封装的对象类型,就可以返回给我们想要的结果。

image-20201003153540356

参考代码:

看看就好,也可以直接拿来用。

package com.kaikeba.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 *
 * @Description :
 * @author: Faker
 * @date : 2020-09-28
 */
public class JdbcUtil {

    /**
     * 自动提交
     */
    private static boolean commit = true;

    /**
     * 自动驼峰命名
     */
    private static boolean camelCase = true;

    /**
     * 设置提交
     *
     * @param commit 提交
     */
    public static void setCommit(boolean commit) {
        JdbcUtil.commit = commit;
    }


    /**
     * 设置自动驼峰命名
     *
     * @param camelCase 自动驼峰命名
     */
    public static void setCamelCase(boolean camelCase) {
        JdbcUtil.camelCase = camelCase;
    }

    /**
     * 获取连接
     *
     * @return {@link Connection}
     */
    private static Connection getConnection() {
        try {
            Connection connection = DruidUtil.getDataSource().getConnection();
            connection.setAutoCommit(commit);
            return connection;
        } catch (SQLException e) {
            throw new RuntimeException("获取连接失败");
        }
    }

    /**
     * 更新
     * 更新方法只要返回影响的行数,返回值都是int
     *
     * @param sql     sql
     * @param objects 对象
     * @return int
     */
    public static int update(String sql, Object... objects) {
        Connection connection = null;
        PreparedStatement statement = null;
        int i = -1;
        try {
            //填充SQL参数
            connection = getConnection();
            statement = connection.prepareStatement(sql);
            int index = 1;
            for (Object object : objects) {
                statement.setObject(index++, object);
            }
            i = statement.executeUpdate();
            return i;
        } catch (SQLException e) {
            e.printStackTrace();
            return i;
        } finally {
            close(connection, statement);
        }
    }

    public static <T> List<Map<String, Object>> queryForMap(String sql, Object... objects) {
        return queryForMap(sql, Object.class, objects);
    }

    /**
     * 将查询结果封装为Map,如果有多条数据则返回一个元素为Map的List集合
     *
     * @param sql       sql
     * @param className 类名
     * @param objects   对象
     * @return {@link T}
     */
    public static <T> List<Map<String, T>> queryForMap(String sql, Class<T> className, Object... objects) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        String methodName = null;
        try {
            connection = getConnection();
            statement = connection.prepareStatement(sql);
            int index = 1;
            //填充SQL参数
            for (Object object : objects) {
                statement.setObject(index++, object);
            }
            resultSet = statement.executeQuery();
            List<Map<String, T>> list = new ArrayList<>();
            //填充查询结果为Java对象
            while (resultSet.next()) {
                Map<String, T> map = handlerMapData(resultSet, className);
                list.add(map);
            }
            return list.isEmpty()?null:list;
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        } finally {
            close(connection, statement, resultSet);
        }
    }

    /**
     * 查询为对象
     * 传入一个className,返回与给定类对象类型相同的对象
     * 目前只能处理包装数据类型,POJO类
     *
     * @param sql     sql
     * @param objects 对象
     * @return {@link T}
     */
    public static <T> T queryForObject(String sql, Class<T> className, Object... objects) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = getConnection();
            statement = connection.prepareStatement(sql);
            int index = 1;
            //填充SQL参数
            for (Object object : objects) {
                statement.setObject(index++, object);
            }
            resultSet = statement.executeQuery();
            if (!resultSet.next()) {
                return null;
            }
            return handlerData(resultSet, className);
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        } finally {
            close(connection, statement, resultSet);
        }
        //填充查询结果为Java对象

    }

    /**
     * 查询为列表
     * 传入一个className,返回与给定类对象类型相同的对象列表
     *
     * @param sql     sql
     * @param objects 对象
     * @return {@link List<T>}
     */
    public static <T> List<T> queryForList(String sql, Class<T> className, Object... objects) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        String methodName = null;
        try {
            connection = getConnection();
            statement = connection.prepareStatement(sql);
            int index = 1;
            //填充SQL参数
            for (Object object : objects) {
                statement.setObject(index++, object);
            }
        }catch (SQLException e) {
            System.out.println("SQL参数异常!");
            close(connection, statement);
            e.printStackTrace();
            return null;
        }
        try {
            resultSet = statement.executeQuery();
            List<T> list = new ArrayList<>();
            //填充查询结果为Java对象
            while (resultSet.next()) {
                list.add(handlerData(resultSet, className));
            }
            return list;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(connection, statement, resultSet);
        }
        return null;
    }


    /**
     * 处理程序数据
     * 这个方法是将结果集封装为对象,可能是封装一个pojo对象,也可能是封装一个Integer普通对象
     * 比如select count(*) from table,也是查询语句,也走ResultSet方法。
     *
     * @param resultSet 结果集
     * @return {@link T}
     */
    private static <T> T handlerData(ResultSet resultSet, Class<T> className) throws SQLException{
        T object = null;
        try {
            //如果是包装数据类型 java.lang下的
            if (className.getName().startsWith("java.lang")) {
                return defaultHandlerData(resultSet, className);
            }
            object = className.newInstance();
            fillInData(object, className, resultSet, false);
        } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return object;
    }

    private static <T> Map<String, T> handlerMapData(ResultSet resultSet, Class<T> className)throws SQLException {
        Map<String, T> object = null;
        try {
            object = new HashMap<>(16);
            fillInData(object, className, resultSet, true);
        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return object;
    }

    /**
     * 填充数据
     *
     * @param object    对象
     * @param className 类名
     * @param resultSet 结果集
     * @param toMap     映射
     * @throws SQLException              sqlexception异常
     * @throws NoSuchMethodException     没有这样的方法异常
     * @throws InvocationTargetException 调用目标异常
     * @throws IllegalAccessException    非法访问异常
     */
    private static <T> void fillInData(Object object, Class<T> className, ResultSet resultSet, boolean toMap) throws SQLException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ResultSetMetaData metaData = resultSet.getMetaData();
        //获取一条查询记录的字段数
        for (int i = 1; i < metaData.getColumnCount() + 1; i++) {
            //取出每一个字段的名字
            String columnName = metaData.getColumnName(i);
            StringBuffer sb = new StringBuffer();
            Class type = matchObject(metaData.getColumnTypeName(i));
            //格式化
            String baseMethodName=columnName;
            if (camelCase){
                for (int j = 0; j < columnName.length(); j++) {
                    if (columnName.charAt(j) == '_') {
                        j++;
                        sb.append(columnName.substring(j, j + 1).toUpperCase());
                    } else {
                        sb.append(columnName.substring(j, j + 1));
                    }
                }
                baseMethodName=sb.toString();
            }
            //调用对象的set方法来填充对象属性
            String[] strings = type.getTypeName().split("\\.");
            String typeName = strings[strings.length - 1];
            //通过字段名判断使用哪个get方法来查询数据
            String handlerMethodName = "get" + ("Integer".equals(typeName) ? "Int" : typeName.substring(0, 1).toUpperCase() + typeName.substring(1, typeName.length()));
            Method handlerMethod = resultSet.getClass().getDeclaredMethod(handlerMethodName, int.class);
            Object arg = handlerMethod.invoke(resultSet, i);
            //调用get方法
            if (toMap) {
                Method method = object.getClass().getDeclaredMethod("put", Object.class, Object.class);
                method.invoke(object, baseMethodName, arg);
            } else {
                String methodName = "set" + baseMethodName.substring(0, 1).toUpperCase() + baseMethodName.substring(1, baseMethodName.length());
                Method method = className.getDeclaredMethod(methodName, type);
                method.invoke(object, arg);
            }
        }
    }

    /**
     * 默认数据的处理程序
     *
     * @param resultSet 结果集
     * @param className 类名
     * @return {@link T}* @throws NoSuchMethodException 没有这样的方法异常
     * @throws SQLException              sqlexception异常
     * @throws InvocationTargetException 调用目标异常
     * @throws IllegalAccessException    非法访问异常
     * @throws InstantiationException    实例化异常
     */
    private static <T> T defaultHandlerData(ResultSet resultSet, Class<T> className) throws NoSuchMethodException, SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {
        ResultSetMetaData metaData = resultSet.getMetaData();
        Constructor<T> constructor = null;
        Method handlerMethod = null;
        String handlerMethodName = null;
        //匹配该字段的类型,将java.lang分割掉,留下来最后的类型
        Class type = matchObject(metaData.getColumnTypeName(1));
        String[] strings = type.getTypeName().split("\\.");
        String typeName = strings[strings.length - 1];
        //如果是Integer,resultSet的get方法是getInt
        //其他的方法都是类型前加get,一一对应
        if ("Integer".equals(typeName)) {
            constructor = className.getConstructor(int.class);
            handlerMethodName = "getInt";
        } else {
            constructor = className.getConstructor(type);
            handlerMethodName = "get" + typeName.substring(0, 1).toUpperCase() + typeName.substring(1, typeName.length());
        }
        //调用resultSet的get方法得到数据
        handlerMethod = resultSet.getClass().getDeclaredMethod(handlerMethodName, int.class);
        Object arg = handlerMethod.invoke(resultSet, 1);
        //调用一参构造方法
        return constructor.newInstance(arg);
    }


    /**
     * 将数据库的字段的数据类型匹配为我们java的数据类型
     *
     * @param columnTypeName 列类型的名字
     * @return {@link Class<?>}
     */
    private static Class<?> matchObject(String columnTypeName) {
        switch (columnTypeName) {
            case "INT":
            case "BIGINT":
                return Integer.class;
            case "TIMESTAMP":
                return Timestamp.class;
            case "VARCHAR":
            case "CHAR":
                return String.class;
            case "FLOAT":
                return Float.class;
            case "DOUBLE":
            case "DECIMAL":
                return Double.class;
            case "DATE":
            case "DATETIME":
                return Date.class;
            default:
                System.out.println(columnTypeName + "类型没有找到对应的匹配类型");
                return Object.class;
        }
    }


    /**
     * 关闭
     *
     * @param connection 连接
     * @param statement  声明
     */
    private static void close(Connection connection, Statement statement) {
        close(connection, statement, null);
    }

    /**
     * 关闭
     *
     * @param connection 连接
     */
    private static void close(Connection connection) {
        close(connection, null, null);
    }

    /**
     * 关闭
     *
     * @param connection 连接
     * @param statement  声明
     * @param resultSet  结果集
     */
    private static void close(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (!commit) {
                connection.rollback();
            }
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值