十念 发表于 2024-9-29 16:14:37

简单的DbUtils工具类【精致】

目次
单条通用增删改方法
        1.创建maven项目,并加载依靠

 2.创建数据库连接工具类(Dbutils类)





3.创建一个执行器(SqlExecutor类)



4.通用(增,删,改)方法





1.创建方法



2.创建userInfo实体类




3.创建测试类,测试增,删,改三个操作



4.末了执行效果


批量(增,删,改)方法




1.在sqlExecutor类中添加一个executeBatch(String,Object[][])方法

2. 测试类中执行 批量(增,删,改)操作



3.末了运行效果


通用查询



战略模式:
1.定义一个范例转换接口 ResultSetHandler
【一】将查询效果转换为Array数组
1.定义ArrayHandler转换类,实现ResultSetHandler接口
2.定义RowProcessor行处置惩罚器
3. 在SqlExecutor类中定义通用查询方法
【二】将查询效果转换为Map集合
1.定义MapHandler类,实现ResultSetHandler
2.在RowProcessor类中实现具体转换map集合的方法
【二.五】测试转换的两个范例(Array和Map)
1.分别创建两个战略
2.末了转换效果

【三】将效果集转换成Bean对象
1.创建一个Cloumn注解【映射数据库名称】
2. 在实体类中加上自定义Column注解
3.创建一个BeanHandler战略类实现ResultSetHandler接口
4.在行RowProcessor行处置惩罚器工具类添加实现转换bean对象的方法

getPropertyDescriptors(Class);
newInstance(Class clazz);
checkColumn(String,PropertyDescriptor, Class);

setBean(String,ResultSet,PropertyDescriptor, T);
5.末了在测试类中举行测试效果
6. 末了执行效果
优化查询效果转bean对象代码
java时间范例类比:
java8之前的时间范例关系
新版本的时间范例
实现思路:

单条通用增删改方法

        1.创建maven项目,并加载依靠

https://i-blog.csdnimg.cn/blog_migrate/9b681933648b60943de9dd14495965e4.png
对应maven的依靠为:
      <!--mysql驱动-->
      <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.2.0</version>
      </dependency>


      <!--单元测试-->
      <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
      </dependency>


      <!--lombok依赖-->
      <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
            <scope>provided</scope>
      </dependency>




 2.创建数据库连接工具类(Dbutils类)

   作用:   用于获取Connection连接对象,连接数据库
package com.xr.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
* 数据库连接工具类
* 作用: 用于获取Connection连接对象
* @author xr
* @Date 2024/5/8 13:10
*/

public class Dbutils {

    /**
   * 数据库驱动
   * @Date 2024/5/8 13:12
   */

    private static final String DRIVER = "com.mysql.cj.jdbc.Driver";

    /**
   * 数据据连接地址
   * jdbc:mysql://localhost:3306/数据库名称?serverTimezone=GMT%2B8"
   * @Date 2024/5/8 13:12
   */

    private static final String URL = "jdbc:mysql://localhost:3306/dbutils?serverTimezone=GMT%2B8";

    /**
   * 数据库用户名
   * @Date 2024/5/8 13:12
   */

    private static final String USER = "root";

    /**
   * 数据库密码
   * @Date 2024/5/8 13:12
   */

    private static final String PASSWORD = "1234";

    /**
   * 获取数据库连接
   * @Date 2024/5/8 13:12
   */
    public static Connection getConnection() {
      // 定义数据库连接对象
      Connection conn = null;


      try {
            // 注册JDBC驱动
            Class.forName(DRIVER);
            // 获取数据库连接
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
      } catch (ClassNotFoundException | SQLException e) {
            throw new RuntimeException("数据数据库连接失败!!!!",e);
      }
      // 返回连接对象
      return conn;
    }
}










3.创建一个执行器(SqlExecutor类)

   作用: 
         封装增,删,查,改操作
package com.xr.utils;

import java.sql.Connection;



/**
* sql 执行器,操作执行sql,通用增删改操作
* 作用: 封装增删改查操作
* @author xr
* @Date 2024/5/8 13:19
*/
public class SqlExecutor {

    /**
   * 定义连接对象
   * @Date 2024/5/8 13:20
   */

    private final Connection conn;

    /**
   * 构造方法,初始化连接对象
   * @Date 2024/5/8 13:20
   */
    public SqlExecutor(Connection conn) {
      this.conn = conn;
    }


}





4.通用(增,删,改)方法

   因为我想使用一个方法就可以完成(增,删,改)操作
1. 比方:
        // 增加数据
        insert into user_info(u_name,u_password,u_age,deposit,brith)
        value(?,?,?,?,?);

        // 删除数据
        delete from user_info where u_name = ?

        // 修改数据
        update user set u_name = ? where id = ?

2. 发现:
        我们的有不同的sql语句,设置不同的值

3. 解决方案: 
        使用PreparedStatement中的预编译sql语句,使用占位数 ? 解决值未知问题

4.思路: 
         1. 定义方法 excuteUpdate(sql,Object... params){};
         2. sql: sql语句  , params : 为多个参数,返回一个任意范例数组
         3. 将sql语句举行预编译操作,设置传递过来的参数
         4. 为什么要将预编译操作抽离出来一个方法,因为可以使方法效果更清晰
         5. 执行sql语句,返回受影响行数
         6.关闭资源










1.创建方法

           executeUpdate(String,Object...) : 通用增,删,改方法
        setPreparedStatement(PreparedStatement,Object...) : sql语句预编译,并设置参数
        close(Statement st) :关闭Statement对象资源
        close(ResultSet rs) : 关闭ResultSet对象资源
        close() :关闭Connection连接对象资源
    /**
   * 通用执行增删改操作方法
   * sql: sql语句
   * params: sql语句中占位符对应的参数
   * @Date 2024/5/8 13:23
   */   
public int executeUpdate(String sql,Object... params) throws SQLException {
      // 判断连接对象是否为空
      if (conn == null) {
            throw new RuntimeException("连接对象为空!!!");
      }

      // 判断sql语句是否为空
      if(sql == null || sql.isEmpty()) {
            throw new RuntimeException("sql语句为空!!!");
      }

      // 定义PreparedStatement对象,作用对sql语句进行预编译
      PreparedStatement ps = null;

      try {
            // 将sql进行预编译,方法中抛出SQL异常
            ps = conn.prepareStatement(sql);

            // 设置sql 中 ?号 占位符对应的参数
            // 比如 sql = "insert into user(name,age) values(?,?)"
            setPreparedStatementParams(ps,params);

            // 执行sql语句,返回影响行数
            return ps.executeUpdate();
      } catch (SQLException e) {
            // 异常重抛,重抛成运行时异常
            throw new RuntimeException("执行sql语句失败!!!",e);
      } finally {
            // 关闭资源
            close(ps);
            close();
      }
    }


    /**
   * 设置sql预编译参数
   * @Date 2024/5/8 9:04
   */

    private void setPreparedStatement(PreparedStatement preparedStatement,Object... params)throws SQLException {
      // insert into user(id,name,age) values(?,?,?)
      // update user set name=?,age=? where id=?
      // 循环的边界为传递过来的params参数长度
      for (int i = 0; i < params.length; i++) {
            // 设置占位符?对应的值,占位符下标从1开始
            preparedStatement.setObject(i+1,params);
      }
    }



    /**
   * 关闭PreparedStatement对象资源
   * @Date 2024/5/8 13:54
   */

    private void close(PreparedStatement ps) {
      if (ps != null) {
            try {
                // 关闭PreparedStatement对象资源
                ps.close();
            } catch (SQLException e) {
                throw new RuntimeException("关闭PreparedStatement对象失败!!!",e);
            }
      }
    }

    /**
   * 关闭连接对象资源
   * @Date 2024/5/8 13:54
   */
    private void close() {
      if (conn != null ) {
            try {
                // 关闭连接对象资源
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException("关闭conn对象资源失败!!!",e);
            }
      }
    }


    /**
   * 关闭ResultSet对象资源
   * @param rs 结果集对象
   */
    private void close(ResultSet rs) {
      if (rs != null) {
            try {
                // 关闭ResultSet对象资源
                rs.close();
            } catch (SQLException e) {
                throw new RuntimeException("关闭ResultSet对象失败!!!",e);
            }
      }
    }







2.创建userInfo实体类

package com.xr.entity;

import com.xr.util.interfaceUtils.Column;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
* userInfo实体
* @Data注解: 拥有get,set方法
* @AllArgsConstructor: 拥有全参数构造方法
* @NoArgsConstructor: 拥有无参构造方法
* @author xr
* @Date 2024/5/8 20:51
*/


@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
    /**
   * id
   */
    private Integer id;
    /**
   * 用户名
   */
    private String username;
    /**
   * 密码
   */
    private String password;
    /**
   * 年龄
   */
    private Integer age;
    /**
   * 存款
   */
    private BigDecimal deposit;
    /**
   * 生日
   */
    private LocalDateTime birth;
}








3.创建测试类,测试增,删,改三个操作

package com.xr;

import com.xr.utils.Dbutils;
import com.xr.utils.SqlExecutor;

import java.math.BigDecimal;
import java.sql.SQLException;
import java.time.LocalDateTime;

/**
* 测试类
* @author xr
* @Date 2024/5/10 10:54
*/

public class TestSql {
    public static void main(String[] args) throws SQLException {

      //*----------------------添加数据-----------------------*/
      // 创建SqlExecutor对象控制器,并传入连接对象
      SqlExecutor sqlExecutor = new SqlExecutor(Dbutils.getConnection());
      // 定义sql语句,[插入数据]
      String sql = "insert into user_info(u_name,u_password,u_age,deposit,birth) values(?,?,?,?,?)";

      // 增加数据操作
      int i = sqlExecutor.executeUpdate(sql, "张三", "123456", 18, new BigDecimal(1000), LocalDateTime.now());

      //输出结果
      System.out.println("插入数据:" + i +"行");


      /*----------------------修改数据-----------------------*/
      sqlExecutor = new SqlExecutor(Dbutils.getConnection());
      // 定义sql语句,[修改数据]
      sql = "update user_info set u_password = ? where u_name = ?";

      // 修改数据操作
      i = sqlExecutor.executeUpdate(sql, "22222","张三");

      //输出结果
      System.out.println("修改数据:" + i +"行");




      /*----------------------删除数据-----------------------*/
      sqlExecutor = new SqlExecutor(Dbutils.getConnection());
      // 定义sql语句,[删除数据]
      sql = "delete from user_info where u_name = ?";

      // 删除数据操作
      i = sqlExecutor.executeUpdate(sql, "张三");

      //输出结果
      System.out.println("删除数据:" + i +"行");
    }
}







4.末了执行效果

https://i-blog.csdnimg.cn/blog_migrate/f642081b3d16ae98a7ed3e864f4de75a.png





批量(增,删,改)方法

   前面定义的方法是 :
        1.单条增加数据
        2.单条修改数据
        3.单条删除数据

如今我想批量做这些操作(多条),就是将多个sql语句参数装数组里面,定义一个Object[][]二维数组,存储参数

思路:
        1.将sql语句举行预编译
        2.将sql语句中的占位符对应的参数设置到sql语句中
        3.将sql语句添加到批量缓存中
        4.批量提交到数据库中








1.在sqlExecutor类中添加一个executeBatch(String,Object[][])方法

   String : sql语句
Object[][] : sql语句的参数
/**
   * 批量执行sql语句(增,删,改)
   * 实现思路:
   * 1.将sql语句进行预编译
   * 2.将sql语句中的占位符对应的参数设置到sql语句中
   * 3.将sql语句添加到批量缓存中
   * 4.批量提交到数据库中
   * @param sql sql语句
   * @param params 参数数组
   * @return 受影响行数
   */
    public int[] executeBatch(String sql,Object[][] params) {
      if (conn == null) {
            throw new RuntimeException("连接对象为空!!!");
      }

      if (sql == null || sql.isEmpty()) {
            throw new RuntimeException("sql语句为空!!!");
      }

      // 定义PreparedStatement对象
      PreparedStatement ps = null;

      try {
            // 将sql语句进行预编译
            ps = conn.prepareStatement(sql);

            // 遍历params数组,将数组中的参数设置到sql语句中
            // 第一个循环遍历params数组,第二个循环遍历params数组中的数组

            for(Object[] param : params) {

                // 设置sql语句中占位符对应的参数
                setPreparedStatementParams(ps,param);

                //将ps的操作缓存到内存中,最后在批量提交到数据库中添加到批量缓存中
                ps.addBatch();
            }

            // 批量提交sql语句
            return ps.executeBatch();
      } catch (SQLException e) {
            throw new RuntimeException("预编译sql语句失败!!!",e);
      } finally {
            close(ps);
            close();
      }
    }




2. 测试类中执行 批量(增,删,改)操作

package com.xr;

import com.xr.utils.Dbutils;
import com.xr.utils.SqlExecutor;

import java.math.BigDecimal;
import java.sql.SQLException;
import java.time.LocalDateTime;

/**
* 测试类
* @author xr
* @Date 2024/5/10 10:54
*/

public class TestSql {
    public static void main(String[] args) throws SQLException {

      //*----------------------批量添加数据-----------------------*/
      // 创建SqlExecutor对象控制器,并传入连接对象
      SqlExecutor sqlExecutor = new SqlExecutor(Dbutils.getConnection());


      // 定义sql语句,[批量插入数据]
      String sql = "insert into user_info(u_name,u_password,u_age,deposit,birth) values(?,?,?,?,?)";

      // 创建二维数组
      Object[][] params = {
                {"李四", "123456", 18, new BigDecimal(1000), LocalDateTime.now()},
                {"王五", "123456", 18, new BigDecimal(1000), LocalDateTime.now()},
                {"张三", "123456", 18, new BigDecimal(1000), LocalDateTime.now()}
      };

      // 批量增加数据操作
      int[] i = sqlExecutor.executeBatch(sql,params );

      //输出结果
      System.out.println("批量添加数据:" + i.length +"行");


      /*----------------------批量修改数据-----------------------*/
      sqlExecutor = new SqlExecutor(Dbutils.getConnection());

      // 定义sql语句,[批量修改数据]
      sql = "update user_info set u_password = ? where u_name = ?";

      // 创建二维数组
      params = new Object[][]{
                {"11111","李四"},
                {"22222","王五"},
                {"33333","张三"}
      };

      // 批量修改数据操作
      i = sqlExecutor.executeBatch(sql, params);

      //输出结果
      System.out.println("批量修改数据:" + i.length +"行");




      /*----------------------批量删除数据-----------------------*/
      sqlExecutor = new SqlExecutor(Dbutils.getConnection());
      // 定义sql语句,[批量删除数据]
      sql = "delete from user_info where u_name = ?";

      //定义二位数组
      params = new Object[][]{
                {"李四"},
                {"王五"},
                {"张三"}
      };

      // 批量删除数据操作
      i = sqlExecutor.executeBatch(sql, params);

      //输出结果
      System.out.println("批量删除数据:" + i.length +"行");
    }
}






3.末了运行效果

https://i-blog.csdnimg.cn/blog_migrate/1f251ed984fc07ef846a1b7cbdc732fd.png





通用查询

   前面完成了增删改操作,就差一个查询了

当从数据库查询出来的效果后,java代码怎样拿到呢???
        1.可以通过Array数组吸取
        2.可以通过map集合吸取
        3.可以将查询出来的效果映射成java对象
        4.假如只查一列的值,直接返回一个任意根本数据范例吸取

这里有4中选项可以选择,将查询出的效果使用多种方式吸取他,假如按照常理的方案写的话,需要写多个if,else很贫苦,所以可以择优简便选择使用战略模式

 






战略模式:

   战略模式:
        我的理解: 当一个东西可以有很多种选择时,就可以考虑使用战略来简化代码了
        官方表明:
                战略模式是一种设计模式,它允许在运行时选择算法的行为。在战略模式中,可以定义一系列算法,并将每个算法封装在单独的类中。这些算法具有雷同的接口,使得它们可以相互替换,而客户端代码则可以根据需要选择合适的算法。

        比如:
                我商城购买了一件东西,付款的时间可有有多种支付方式,如:支付宝支付,微信支付,银联支付,这时也是有多种选择



1.定义一个范例转换接口 ResultSetHandler

   作用:
        是封装效果集不同处置惩罚方法,将查询的效果转换成不同的形式

ResultSet :效果集,我们通过效果集来转化为其他范例
package com.xr.util;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
* 抽象的结果集处理器
* @author xr
* @Date 2024/5/8 9:58
*/

public interface ResultSetHandler<T> {
    /**
   * 结果集的处理方法
   * @param rs 结果集对象
   * @return 处理后的对象,因为返回的对象是未知类型,定义成泛型
   * @throws SQLException sql异常
   */
    T handle(ResultSet rs) throws SQLException;
}
【一】将查询效果转换为Array数组

   实现思路: 
        1. 定义一个ArrayHandler类,实现ResultSetHandler接口
        2. 定义一个RowProcessor工具类,具体的实现转换方法写在该类
        2. 重写Handle方法,调用RowProcessor工具类中的具体转换实现

为什么不直接到ArrayHandler类中直接写转换的实现,而是又定义一个RowProcessor工具类来实现呢?
           1. 因为要克制将具体的实现逻辑暴露给调用者,因为调用者并不需要知道怎样将效果集转换为数组,而是应该只关心怎样将效果集转换为数组,所以将具体的实现逻辑写在RowProcessor中,也遵循单一职责原则

        2.看RowProcessor这个类名,他是一个行处置惩罚工具类,所以如今将查询出来的效果转换成其他范例都只是转换1条数据,假如多条数据需要转换的话,我们也可以调用行处置惩罚器中的实现,简便代码

1.定义ArrayHandler转换类,实现ResultSetHandler接口

   作用:
        将查询出来的数据(单条)转换成数组
package com.xr.result;

import com.xr.utils.ResultSetHandler;
import com.xr.utils.RowProcessor;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
* 数组转换类
* @author xr
* @Date 2024/5/10 19:31
*/

public class ArrayHandler implements ResultSetHandler<Object[]> {

    /**
   * 将结果集转换为数组
   * @param rs 结果集
   * @return 返回转换后的数组
   * @throws SQLException 抛出sql异常
   */
    @Override
    public Object[] handle(ResultSet rs) throws SQLException {
      // 如果结果集有数据,则调用工具类中的方法将结果集转换为数组,否则返回null
      return rs.next() ? RowProcessor.toArray(rs):null;
    }
}

2.定义RowProcessor行处置惩罚器

   作用:
        将转换多个范例具体操作全部写在本类,实施单一职责
    在类中定义toArray()方法
      作用: 将查询出的数据效果集转换成数组
package com.xr.utils;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
* 行处理器工具类
* @author xr
* @Date 2024/5/10 19:34
*/

public class RowProcessor {

    /**
   * 将结果集转换为数组
   * @param rs 数据集对象
   * @return 转换后的数组
   * @throws SQLException 抛出sql异常
   */
    public static Object[] toArray(ResultSet rs) throws SQLException {
      // 定义数组,使用结果集的元数据中的列数作为数组长度
      Object[] result = new Object;

      // 遍历结果集
      for(int i = 0; i< result.length; i++) {
            // 遍历数组,将结果集的每一列的值赋值给数组,通过结果集的下标获取具体值
            result = rs.getObject(i+1);
      }

      // 返回数组
      return result;
    }
}
3. 在SqlExecutor类中定义通用查询方法

   executeQuery(String,ResultSetHandler,Object....)
        作用:
                通用返回效果类,并将查询的效果转换成指定的范例
String :sql语句
ResultSetHandler:指定转换范例的实现类
Object...: 参数数组

/**
   * 执行查询sql语句,返回指定类型
   * 实现思路:
   * 1.将sql语句进行预编译
   * 2.将sql语句中的占位符对应的参数设置到sql语句中
   * 3.执行sql语句,返回结果集
   * 4.调用处理器,将查询的结果转换成指定类型
   * 5.返回转换之后的类型
   * @param sql sql语句
   * @param handler 结果集处理器, 将结果集转换为指定类型
   * @param params 参数数组
   * @return 返回转换之后的类型
   */
    public <T> T executeQuery(String sql,ResultSetHandler<T> handler,Object... params) {
      if (conn == null) {
            throw new RuntimeException("连接对象为空!!!");
      }

      if (sql == null || sql.isEmpty()) {
            throw new RuntimeException("sql语句为空!!!");
      }
      if (handler == null) {
            throw new RuntimeException("ResultSetHandler为空!!!");
      }

      // 定义PreparedStatement对象
      PreparedStatement ps = null;
      // 声明结果集对象
      ResultSet rs = null;

      // 定义转换后的结果
      T result = null;

      try {
            // 将sql语句进行预编译
            ps = conn.prepareStatement(sql);

            // 设置sql语句中占位符对应的参数
            setPreparedStatementParams(ps,params);

            //执行sql
            rs = ps.executeQuery();

            // 调用处理器,将查询的结果转换成指定类型
            result = handler.handle(rs);

            // 返回转换后的类型
            return result;


      }catch (SQLException e) {
            throw new RuntimeException("预编译sql语句失败!!!",e);
      } finally {
            // 关闭ResultSet结果集资源
            close(rs);
            // 关闭PreparedStatement对象资源
            close(ps);
            // 关闭Connection连接对象资源
            close();
      }
    }


【二】将查询效果转换为Map集合

   前面已经实现第一个战略,将查询的效果转换成Array数组

如今将查询效果转换成Map集合,同样也是将这个战略实现ResultSetHandler接口
实现思路:
        1.定义一个类MapHandler,并实现ResultsetHandler接口,重写handle方法
        2.在行转换器RowProcess类中写出转换成map范例方法的具体实现
        3.查询的效果字段作为map集合的key,具体值作为map集合的value
1.定义MapHandler类,实现ResultSetHandler

package com.xr.result;

import com.xr.utils.ResultSetHandler;
import com.xr.utils.RowProcessor;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;

/**
* 将查询的结果ResultSet结果集转换为Map集合
* @author xr
* @Date 2024/5/11 8:42
*/

public class MapHandler implements ResultSetHandler<Map<String,Object>> {

    /**
   * 将查询的结果ResultSet结果集转换为Map集合
   * @param rs 结果集
   * @return 返回转换后的Map集合
   * @throws SQLException sql异常
   */
    @Override
    public Map<String, Object> handle(ResultSet rs) throws SQLException {
      // 如果结果集有数据,则返回Map集合,否则返回null
      return rs.next() ? RowProcessor.toMap(rs) : null;
    }
}
2.在RowProcessor类中实现具体转换map集合的方法

    /**
   * 将查询结果转换成map
   * 思路:
   * 1. 获取结果集的元数据
   * 2. 获取结果集的列数
   * 3. 遍历结果集,将结果集的每一列的值赋值给map,通过结果集的下标获取具体值
   * 4. map集合中的key为列名,value为列的值
   * 5. 返回map
   * @param rs 数据集对象
   * @return 返回转换后的map集合
   * @throws SQLException sql异常
   */
    public static Map<String,Object> toMap(ResultSet rs) throws SQLException {
      // 定义map数组
      Map<String,Object> result = new HashMap<>();

      //循环遍历结果结合
      for(int i = 0; i<rs.getMetaData().getColumnCount(); i++) {
            // 获取列名
            String columnName = rs.getMetaData().getColumnName(i+1);
            // 获取列值
            Object object = rs.getObject(i + 1);
            // 将列名和列值添加到map集合中
            result.put(columnName,object);
      }
      // 返回map数组
      return result;
    } 【二.五】测试转换的两个范例(Array和Map)

1.分别创建两个战略

package com.xr;

import com.xr.result.ArrayHandler;
import com.xr.result.MapHandler;
import com.xr.utils.Dbutils;
import com.xr.utils.ResultSetHandler;
import com.xr.utils.SqlExecutor;

import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;

/**
* 测试类
* @author xr
* @Date 2024/5/10 10:54
*/

public class TestSql {
    public static void main(String[] args) throws SQLException {

      /*----------------------转换Array数组-----------------------*/
      // 创建SqlExecutor对象控制器,并传入连接对象
      SqlExecutor sqlExecutor = new SqlExecutor(Dbutils.getConnection());

      // 定义sql语句,[查询单条数据]
      String sql = "select * from user_info where u_name = ?";

      /* 将查询的结果转换成Array数组*/
      //1.创建Array策略
      ResultSetHandler<Object[]> arrayHandler = new ArrayHandler();

      //2.调用sqlExecutor工具类的executeQuery方法,传入sql语句和策略和具体参数
      Object[] objects = sqlExecutor.executeQuery(sql, arrayHandler, "xr");

      // 查看转换结果
      Arrays.stream(objects).forEach(System.out::println);

      System.out.println("-------------------------------转换数组完成------------------------------------");


      /*----------------------转换Array数组-----------------------*/
      // 创建SqlExecutor对象控制器,并传入连接对象
      sqlExecutor = new SqlExecutor(Dbutils.getConnection());

      // 定义sql语句,[查询单条数据]
      sql = "select * from user_info where u_name = ?";

      /* 将查询的结果转换成map集合*/
      // 1. 创建Map策略
      ResultSetHandler<Map<String,Object>> result = new MapHandler();

      //2.调用sqlExecutor工具类的executeQuery方法,传入sql语句和策略和具体参数
      Map<String, Object> map = sqlExecutor.executeQuery(sql, result, "xr");

      // 循环输出结果
      map.forEach((k,v)->{
            System.out.println("字段名:"+k+"---->字段值:"+v);
      });

    }
}
2.末了转换效果

https://i-blog.csdnimg.cn/blog_migrate/92cddfee4789ea741b8dcdaa85bac256.png


【三】将效果集转换成Bean对象

   将查询的效果转换成bean对象时解决难点:
        1.对象属性名称与数据库字段名称不一样
        2.怎样将查询出来的值设置到对象属性中
        3.时间转换问题
解决方案:
        1.添加自定义注解,解决属性名称与数据库字段名称不同等
        2.通过反射的内省机制来设置对象属性值
        3.因为有多种时间范例,mysql版本8之前只有Date时间范例,而mysql8之后多了LocalDateTime时间范例
1.创建一个Cloumn注解【映射数据库名称】

   作用:
        将数据库库字段名称映射在实体类中的属性上
package com.xr;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 映射数据库名称
* @author xr
* @Date 2024/5/11 9:37
* @Retention 注解在运行时有效
* @Target 注解作用范围,使用范围在属性上
*/

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {

    /**
   * 数据库字段名
   */
    String name();
}
2. 在实体类中加上自定义Column注解

package com.xr.entity;

import com.xr.Column;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
* userInfo实体
* @author xr
* @Date 2024/5/8 20:51
*/


@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
    /**
   * id
   */
    @Column(name="u_id")
    private Integer id;
    /**
   * 用户名
   */
    @Column(name="u_name")

    private String username;
    /**
   * 密码
   */
    @Column(name="u_password")
    private String password;
    /**
   * 年龄
   */
    @Column(name="u_age")
    private Integer age;


    /**
   * 存款
   * 后面这两个字段与数据库名称一样,所以不需要添加@Column注解
   */
    private BigDecimal deposit;
    /**
   * 生日
   */
    private LocalDateTime birth;
}
3.创建一个BeanHandler战略类实现ResultSetHandler接口

   为什么要定义class对象?
        1.当我们使用实体类中的属性名与数据库字段名举行匹配时,需要用到反射获取注解和属性
        2.当我们给实体类中的属性设置值时,需要通过反射的内省机制来设置
        3.一切使用到的反射操作,都需要先通过获取Class对象才气使用
package com.xr.result;

import com.xr.utils.ResultSetHandler;
import com.xr.utils.RowProcessor;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
* 将结果集转换成bean对象
* @author xr
* @Date 2024/5/11 9:15
*/

public class BeanHandler<T> implements ResultSetHandler<T> {

    /**
   * 定义class对象
   */
    private Class<T> clazz;


    /**
   * 构造方法传递类对象
   * @param clazz 类对象
   */
    public BeanHandler(Class<T> clazz)
    {
      this.clazz = clazz;
    }


    @Override
    public T handle(ResultSet rs) throws SQLException {
      // 如果有数据,则返回bean对象,否则返回null
      return rs.next()? RowProcessor.toBean(rs,clazz) :null;
    }
}
4.在行RowProcessor行处置惩罚器工具类添加实现转换bean对象的方法


    /**
   * 将查询结果转换成Bean对象
   * 思路:
   * 1.我要将查询出来的数据转化为对象
   * 2.数据源rs,拥有数据库所有字段名称,及对应的数据值
   * 3.clazz,通过clazz对象可以获取里面的所有属性
   * 4.rs里面的字段名称作为clazz对象的属性
   * 5.将rs里面的数据值赋值给clazz对象的属性的属性值
   * 6.由于rs里面的字段名称和clazz对象的属性名称可能不一样
   * 7.定义了注解@Cloumn进行映射对应的数据库名称
   * 8.如果属性有@Cloumn注解,则获取对应的属性名称,判断是否和数据库字段一样,一样则赋值
   * 9.如果属性没有@Cloumn注解,则获取属性名称,因为没有@Cloumn注解,证明属性名称和数据库字段一样
   * 10.设置值的时候肯能有时间类型转换问题
   * 11.为什么会有时间问题呢,因为mysql的驱动5和mysql的驱动8,时间类型不一样
   * 12.设置完成将返回对应的对象
   * @param rs 结果集
   * @param clazz 对象对应的类
   * @return 返回转换后的bean对象
   * @throws SQLException sql异常
   */
    public static <T> T toBean(ResultSet rs,Class<T> clazz) throws SQLException {

      // 获取类中的所有属性,通过内省机制获取
      PropertyDescriptor[] pds = getPropertyDescriptors(clazz);

      // 获取对象实例,由于getConstructor,newInstance需要抛出异常,
      // 则将他们提取出来,定义另一个方法抛出异常
      T bean = newInstance(clazz);

      for (int i = 0 ; i < rs.getMetaData().getColumnCount(); i++) {
            /* 获取列名,通过类名判断是否与属性名一样 */
            String columnLabel = rs.getMetaData().getColumnLabel(i+1);

            // 循环遍历属性
            for (PropertyDescriptor pd : pds) {
                // 判断属性名称和数据库字段是否一样
                if(checkColumn(columnLabel,pd,clazz)) {
                  // 属性名称和数据库字段一样,则设置对应的值

                  /*
                  * columnLabel:数据库字段名称
                  * rs:结果集,拥有所有数据库字段名称及对应的数据值
                  * pd:属性描述器,属性名称
                  * bean:对象实例
                  * */
                  setBean(columnLabel,rs,pd,bean);
                  break;
                }
            }
      }
      return bean;
    }
   1.toBean(Result,Class)方法,rs效果集对象,Class为类对象;
2.这个方法里面又调用了其他4个方法
        1.getPropertyDescriptors(Class<?> class)         获取所有属性
        2.newInstance(Class);        获取对象实例
        3.checkColumn(Stirng,PropertyDescriptor, Class<?> )   判定数据库字段名称是否与属性名称同等
        4.setBean(String,ResultSet,PropertyDescriptor,T)          设置对象的属性值
3.为什么要定义这4方法呢????
        1. 因为这些方法都需要抛出异常,假如全部写在toBean方法内,使得代码的阅读性糟糕


getPropertyDescriptors(Class<?>);

   参数:
        Class<?> : 实体类的Class对象
作用:
        获取实体类中所有属性;
在RowProcessor行处置惩罚器类中添加该方法
    /**
   * 获取类中的所有属性【内省机制获取】
   * @param clazz 类
   * @return 返回属性数组
   */
    private static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) {

      try {
            // 创建BeanInfo对象
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz, Object.class);

            // 获取所有属性,返回属性数组
            return beanInfo.getPropertyDescriptors();

      } catch (IntrospectionException e) {
            throw new RuntimeException("获取属性描述器失败",e);
      }
    }

newInstance(Class<T> clazz);

   参数:
        Class:实体类的Class对象
作用:
        获取实体类对象的实例
在RowProcessor行处置惩罚器类中添加该方法
    /**
   * 获取对象实例
   * @param clazz 类的class对象
   * @return 返回对象实例
   */
    private static <T> T newInstance(Class<T> clazz) {
      try {
            return clazz.getConstructor().newInstance();
      } catch (Exception e) {
            throw new RuntimeException("创建对象失败!!!",e);
      }
    }
checkColumn(String,PropertyDescriptor, Class<?>);

   参数:
                    String :         数据库字段名称
PropertyDescriptor:       属性
               Class<?>:         实体类的Class对象
作用 :
        判定属性名称和数据库字段是否一样
在RowProcessor行处置惩罚器类中添加该方法
    /**
   * 判断是否与属性名一样
   */
    private static boolean checkColumn(String columnName, PropertyDescriptor pd, Class<?> clazz) {
      // 获取属性名称
      String beanName = pd.getName();

      try {
            // 通过属性名称获取对象的属性
            Field fieldName = clazz.getDeclaredField(beanName);

            // 判断属性是否有注解
            // 如果有注解,代表属性名称与数据库字段名称不一样,需要进行对比
            // 如果没有,则代表属性名称与数据库名称一样
            if (fieldName.isAnnotationPresent(Column.class)) {
                // 获取属性上的注解值
                beanName = fieldName.getAnnotation(Column.class).name();
            }
            return beanName.equalsIgnoreCase(columnName);
      } catch (NoSuchFieldException e) {
            throw new RuntimeException("判断属性名称失败!",e);
      }
    }


setBean(String,ResultSet,PropertyDescriptor, T);

   参数:
                    String :         数据库字段名称
               ResultSet:         效果集
PropertyDescriptor:       属性
                           T :       对象
作用:
        设置实体类属性对应的值
在RowProcessor行处置惩罚器类中添加该方法
/**
   *
   * @param columnLabel 列名
   * @param rs 数据集
   * @param pd 属性
   * @param instance 实例对象
   * @param <T> 返回转换后的对象
   */
    private static <T> void setBean(String columnLabel,ResultSet rs,PropertyDescriptor pd, T instance) {
      try {
            // 通过名称获取数据库查询的具体值
            Object value = rs.getObject(columnLabel);
            // 如果是基本类型,并且获取的值为空,直接返回
            if(pd.getPropertyType().isPrimitive() && value == null) {
                return;
            }
            // 通过传递过来来的字段获取set方法,并调用通过invoke方法调用set方法
            pd.getWriteMethod().invoke(instance,value);

      } catch (SQLException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException("获取具体值失败,设置具体值失败!!!",e);
      }

    }
5.末了在测试类中举行测试效果

package com.xr;

import com.xr.entity.UserInfo;
import com.xr.result.BeanHandler;
import com.xr.utils.Dbutils;
import com.xr.utils.SqlExecutor;

import java.sql.SQLException;

/**
* 测试类
* @author xr
* @Date 2024/5/10 10:54
*/

public class TestSql {
    public static void main(String[] args) throws SQLException {

      /*----------------------转换Bean对象-----------------------*/
      // 创建SqlExecutor对象控制器,并传入连接对象
      SqlExecutor sqlExecutor = new SqlExecutor(Dbutils.getConnection());

      // 定义sql语句,[查询单条数据]
      String sql = "select * from user_info where u_name = ?";

      // 创建Bean策略
      BeanHandler<UserInfo> userInfoBeanHandler = new BeanHandler<>(UserInfo.class);

      //调用sqlExecutor工具类的executeQuery方法,传入sql语句和策略和具体参数
      UserInfo xr = sqlExecutor.executeQuery(sql, userInfoBeanHandler, "xr");

      // 输出结果
      System.out.println(xr);
      System.out.println("-------------------------------转换bean对象完成------------------------------------");
    }
}
6. 末了执行效果

https://i-blog.csdnimg.cn/blog_migrate/4748ef1601c21b1366b429595177ea94.png
优化查询效果转bean对象代码

   前面将查询的效果转换Bean对象还有关于时间范例问题:
        1.当使用MySQL驱动版本5.x时,由于其对Java 8中的新日期时间范例(如LocalDateTime)的支持有限,大概无法直接将其映射到数据库中的日期时间范例。因此,你大概需要手动举行范例转换或者使用额外的库来处置惩罚这种环境。
而当你使用MySQL驱动版本8.x时,它通常会更好地支持Java 8中的日期时间范例。因此,LocalDateTime可以直接映射到数据库中的相应范例,比如MySQL的DATETIME范例。
        比如:
                <mysql驱动版本5.x的版本>
                java范例                数据库范例
           LocalDateTime           Timestamp         // 需要转换


                <mysql驱动版本8.x的版本>
                java范例                数据库范例
           LocalDateTime          DateTime
        2.在较新的数据库驱动程序中,它们通常会更好地支持Java 8中的日期时间范例,因此你可以直接将Java中的LocalDateTime对象存储到数据库中的相应日期时间列中,而无需手动转换。这是因为新的驱动程序已经具备了将Java 8日期时间范例映射到数据库范例的功能。
然而,
        3. 在较旧的数据库驱动程序中,它们大概没有直接支持Java 8日期时间范例的功能,因此你大概需要手动将LocalDateTime转换为数据库支持的日期时间范例(如java.sql.Timestamp),以便正确地将数据存储到数据库中。
        4.总结
                假如使用mysql5.x版本的驱动,数据库那边没有支持直接存储的范例,所以要手动转换成数据库支持的时间范例,而mysql8.x版本的驱动,则直接支持存储
java时间范例类比:



java8之前范例java8的新增范例java.util.Datejava.time.LocalDatejava.sql.Datejava.time.LocalTimejava.sql.Timejava.time.LocalDateTimejava.sql.Timestampjava.time.ZonedDateTime/java.time.OffsetDateTime/java.time.Instant/java.time.Duration/java.time.Period java8之前的时间范例关系

   在 Java 8 之前的旧版本中,日期时间范例的继续关系如下:

[*] java.util.Date:是一个表示特定时间点的类,准确到毫秒。它是所有日期时间范例的基础。

[*]java.util.Date 继续了 java.lang.Object 类。

[*] java.sql.Date:表示 SQL DATE 范例的日期值,只包含日期信息,不包含时间信息。

[*]java.sql.Date 继续了 java.util.Date。

[*] java.sql.Time:表示 SQL TIME 范例的时间值,只包含时间信息,不包含日期信息。

[*]java.sql.Time 继续了 java.util.Date。

[*] java.sql.Timestamp:表示 SQL TIMESTAMP 范例的日期时间值,包含日期和时间信息。

[*]java.sql.Timestamp 继续了 java.util.Date。

                                                  java.util.Date <-----------------
                                                /        |          \                           \
                                               /         |            \                           \ 
                                              /          |              \                           \
                                            /            |                \                           \ 
                    java.sql.Date java.sql.Time java.sql.Time java.sql.Timestamp 
新版本的时间范例

   
[*] java.time.LocalDate:表示日期,不包含时间信息。
[*] java.time.LocalTime:表示时间,不包含日期信息。
[*] java.time.LocalDateTime:表示日期和时间,不包含时区信息。
[*] java.time.ZonedDateTime:表示带时区的日期时间。
[*] java.time.OffsetDateTime:表示带偏移量的日期时间。
[*] java.time.Instant:表示时间线上的一个特定点,通常用于呆板处置惩罚。
这些类之间没有继续关系,但它们都实现了 java.time.temporal.Temporal 接口,这使得它们具有共同的日期时间操作和方法。这种设计使得新的日期时间 API 更加模块化和易于使用。

实现思路:

   解决难点:
        1.mysql驱动不确定使用的是老版本还是新版本
        2.将未知数据库的时间范例转换成不同的java中的时间范例
解决方案:
        1.通过数据库的字段范例来判定是哪种版本
                比方:
                               使用《mysql5.x.x》 老版本
                        java范例                           数据库范例
       假如   LocalDateTime      对应的        DateTime   

        java中获取数据库DateTime范例为java.sql.Timestamp,那么java范例中为LocalDateTime,需要转换

                               使用《mysql5.x.x》 新版本
                        java范例                           数据库范例
       假如   LocalDateTime      对应的        DateTime   
         java中获取数据库DateTime范例为java.time.LocalDateTime,那么java范例中为LocalDateTime,则不需要转换
     使用不同的mysql驱动版本,从数据库获取的范例不一样
  2.因为java8之后有多种时间范例,这时间是不是有多种选项,这个时间假如按照常理解决方案来做需要用到很多if,else,太贫苦了,所以我们使用战略模式来解决
    实现目的:
        讲查询的时间范例转换为java属性的时间范例
具体流程
        1. 在java代码获取数据库中的时间范例
        2. 在获取实体类的时间范例
        3. 判定实体类中的范例为什么时间范例
        4. 将查询出来的效果转成对应的实体类中的实体范例
        5. 由于java8之后新增了许多时间范例,我们要转换成不同的范例,这时间就可以使用战略模式举行代码优化了



------------------------------------------------------------待更新------------------------------------------------------------

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 简单的DbUtils工具类【精致】