1.Dbs封装
每一个数据库都对应着一个dao 每个dao势必存在公共部分 我们需要将公共部分抽取出来 封装成一个工具类 保留个性化代码即可
- 我们的工具类一般命名为xxxs 比如Strings 就是字符串相关的工具类 而工具类 我们将其放置于util包中
- 我们以是否有<T>区分泛型方法和非泛型方法
- 建议先创建语句、传递参数之后 在执行语句 即不要着急将ResultSet放置在try-with-resources中
- 你可以通过live template快速生成一些代码模板 比如itar可以帮助我们快速生成遍历数组元素的模板 我们也可以自定义动态模板
- dao中需要传递一段个性化代码(即ResultSet->bean)给dbs 要求dbs存在一个接口(用于将数据库中每一行ResultSet映射为bean)用于接收代码 同时可以设置一个row 用于显示当前所在的行号(数据库中的行号一般从0开始计数 列号一般从1开始计数)
- 既然存在dbs 那么以前的三个常量URL、USERNAME、PASSWORD就可以作为公共部分存放在dbs中
- 代码实现
- Dbs.java
package com.axihh.util;import com.axihh.Constants;import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List;public class Dbs {// 常量public static final String URL = "jdbc:mysql://localhost:3306/crm?serverTimezone=UTC";public static final String USERNAME = "root";public static final String PASSWORD = "root";// 定义一个方法 用于存放数据库保存的公共代码public static boolean update(String sql, Object ...args) {try {// 注册驱动Class.forName("com.mysql.cj.jdbc.Driver");try(Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);PreparedStatement pstmt = conn.prepareStatement(sql)) {// 设置参数for (int i = 0; i < args.length; i++) {pstmt.setObject(i + 1, args[i]);}// 执行sql语句return pstmt.executeUpdate() > 0;}}catch(Exception e) {e.printStackTrace();// 执行到此 说明没有任何行受到影响return false;}}// 定义一个方法 用于存放用户信息集合获取的公共代码public static <T> List<T> list(String sql, RowMapper<T> rm, Object ...args) {try {// 注册驱动Class.forName("com.mysql.cj.jdbc.Driver");try(Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);PreparedStatement pstmt = conn.prepareStatement(sql)) {// 建议不要将resultset写入try-with-resources中 因为我们要保持顺序的先后性 先创建语句 在执行语句 保持这样的顺序// 设置参数for (int i = 0; i < args.length; i++) {pstmt.setObject(i + 1, args[i]);}// 执行sql语句ResultSet rs = pstmt.executeQuery();List<T> array = new ArrayList<>();// resultset映射beanfor(int row = 0; rs.next(); row++) {array.add(rm.map(rs, row));}return array;}}catch(Exception e) {e.printStackTrace();// 如果出现异常 证明返回值为空return null;}}// 定义一个接口 用于接收resultset映射bean的代码 具体就是数据库中的每一行对应的resultset映射为beanpublic interface RowMapper<T> {public T map(ResultSet rs, int row) throws Exception;} }
- CustomerDao.java
package com.axihh.dao;import com.axihh.bean.Customer; import com.axihh.util.Dbs;import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import com.axihh.util.Dbs.RowMapper; import java.util.List;public class CustomerDao {public boolean save(Customer customer){String sql = "INSERT INTO customer(name, age, height) VALUES (?, ?, ?)";return Dbs.update(sql, customer.getName(), customer.getAge(), customer.getHeight());}public List<Customer> list() {String sql = "SELECT id, name, age, height FROM customer";return Dbs.list(sql, (rs, row) -> {Customer customer = new Customer();customer.setId(rs.getInt("id"));customer.setName(rs.getString("name"));customer.setAge(rs.getInt("age"));customer.setHeight(rs.getDouble("height"));System.out.println(row + "_" + customer);return customer;});} }
2.缺陷
虽然 我们将dao的公共代码抽取到dbs中以期简化了dao 但是仍然存在着一些问题 常量(比如PASSWORD)动态修改时 需要打开源码修改 再重新编译打包 显然很麻烦
- 解决方案:利用配置文件帮助我们将一些需要动态修改的值放入配置文件中 以代替在写死的Java代码
1.配置文件
- 常见的配置文件
- properties:比较单一 适合量小、简单的配置
- key、value的分隔符是=、:
- 建议分隔符左右不要留空格
- #、!开头是单行注释
- 可以用\来连接多行内容(内容太多一行中无法显示)
- xml:比较灵活 适合量大、复杂的配置
- properties:比较单一 适合量小、简单的配置
2.properties解决方案
- 首先新建一个properties文件 具体就是在src下右键选择resource bundle
- 编写properties内容 以键值对的形式定义三个动态修改的变量(不包含任何的修饰符和字符串双引号 仅仅只是键和值)
- 读取properties内容 根据键找值的方式获取相关值写入Dbs中对应的变量 由于变量支持动态修改 所以不在通过final修饰 并且变量不再是常量 所以无需大写
- 我们将读取properties 写入三个动态修改的变量代码内置于静态初始化块中 表明程序运行过程中仅需进行一次赋值
- 调用class获取当前类的字节码文件 然后通过getClassLoader获取加载该字节码文件的类加载器 然后通过getResourceAsStream获取资源文件 并且通过字节输入流读取该资源文件
- 定义Properties对象 将资源文件加载到该对象中
- 通过键找值的方式 完成对Dbs文件中三个动态修改变量的赋值操作
- 注意 其中InputStream属于资源 需要关闭以释放 通过try-with-resources将其自动关闭
package com.axihh.util;import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;public class Dbs {// 常量public static String url;public static String username;public static String password;static {try(// 通过当前类获取字节码文件 然后通过字节码文件获取类加载器 然后通过类加载器获取资源文件 并且通过字节输入流读取该资源文件InputStream is = Dbs.class.getClassLoader().getResourceAsStream("db.properties")) {// 创建properties对象 然后将字节输入流读取的内容加载进该对象中Properties properties = new Properties();properties.load(is);// 然后通过键找值的方式获取指定变量对应的值url = properties.getProperty("url");username = properties.getProperty("username");password = properties.getProperty("password");} catch (IOException e) {e.printStackTrace();}}// 定义一个方法 用于存放数据库保存的公共代码public static boolean update(String sql, Object ...args) {try {// 注册驱动Class.forName("com.mysql.cj.jdbc.Driver");try(Connection conn = DriverManager.getConnection(url, username, password);PreparedStatement pstmt = conn.prepareStatement(sql)) {// 设置参数for (int i = 0; i < args.length; i++) {pstmt.setObject(i + 1, args[i]);}// 执行sql语句return pstmt.executeUpdate() > 0;}}catch(Exception e) {e.printStackTrace();// 执行到此 说明没有任何行受到影响return false;}}// 定义一个方法 用于存放用户信息集合获取的公共代码public static <T> List<T> list(String sql, RowMapper<T> rm, Object ...args) {try {// 注册驱动Class.forName("com.mysql.cj.jdbc.Driver");try(Connection conn = DriverManager.getConnection(url, username, password);PreparedStatement pstmt = conn.prepareStatement(sql)) {// 建议不要将resultset写入try-with-resources中 因为我们要保持顺序的先后性 先创建语句 在执行语句 保持这样的顺序// 设置参数for (int i = 0; i < args.length; i++) {pstmt.setObject(i + 1, args[i]);}// 执行sql语句ResultSet rs = pstmt.executeQuery();List<T> array = new ArrayList<>();// resultset映射beanfor(int row = 0; rs.next(); row++) {array.add(rm.map(rs, row));}return array;}}catch(Exception e) {e.printStackTrace();// 如果出现异常 证明返回值为空return null;}}// 定义一个接口 用于接收resultset映射bean的代码 具体就是数据库中的每一行对应的resultset映射为beanpublic interface RowMapper<T> {public T map(ResultSet rs, int row) throws Exception;}
}
- 细节
我们将properties文件创建在src中 目的在于经过编译打包这一系列操作之后 该资源文件被内置于classes目录中 经过查询 可知该目录中正好是存放src下字节码文件的位置 类加载器将字节码文件加载到JVM中 前提肯定是知道classes目录的位置 那么我们就可以利用这一点通过类加载器找到classes中的properties文件