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映射bean for(int row = 0; rs.next(); row++) { array.add(rm.map(rs, row)); } return array; } }catch(Exception e) { e.printStackTrace(); // 如果出现异常 证明返回值为空 return null; } } // 定义一个接口 用于接收resultset映射bean的代码 具体就是数据库中的每一行对应的resultset映射为bean public 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映射bean
for(int row = 0; rs.next(); row++) {
array.add(rm.map(rs, row));
}
return array;
}
}catch(Exception e) {
e.printStackTrace();
// 如果出现异常 证明返回值为空
return null;
}
}
// 定义一个接口 用于接收resultset映射bean的代码 具体就是数据库中的每一行对应的resultset映射为bean
public interface RowMapper<T> {
public T map(ResultSet rs, int row) throws Exception;
}
}
- 细节
我们将properties文件创建在src中 目的在于经过编译打包这一系列操作之后 该资源文件被内置于classes目录中 经过查询 可知该目录中正好是存放src下字节码文件的位置 类加载器将字节码文件加载到JVM中 前提肯定是知道classes目录的位置 那么我们就可以利用这一点通过类加载器找到classes中的properties文件