JDBC基础
利用反射及JDBC元数据编写通用的查询方法
为什么要用反射?
上一章,我们使用PreparedStatement执行查询数据库操作,但是却存在缺陷,我们这一期就解决这些问题
那么有哪些问题呢?
1、 JDBCUtils的select()方法缺乏普适性,不再是通用的查询方法;
2、 如果将结果集ResultSet的放到调用层解析那么就存在耦合的现象,我们需要解耦;
3、 如果不解析,那么我们可能无法得到对象,而是一个结果集,我们需要面向对象;
结合上面的问题,提炼需要完成的任务要素:
1、 解耦合;
2、 通用的查询方法;
3、 面向对象;
4、 调用层无需过多解析;
那么我们开始吧~
一、数据库的准备
执行以下SQL,生成数据库和数据表并注入数据
/*MySQL - 5.6.12 : Database - test*/
/*创建test数据库*/
CREATE DATABASE test;
/*使用test数据库*/
USE test;
/*创建student表*/
CREATE TABLE student (
id int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
student_name varchar(10) COLLATE utf8_bin DEFAULT NULL COMMENT '学生姓名',
student_sex varchar(10) COLLATE utf8_bin DEFAULT NULL COMMENT '学生性别',
student_number int(11) DEFAULT NULL COMMENT '学生学号',
school varchar(30) COLLATE utf8_bin DEFAULT NULL COMMENT '所属学校',
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*向student表添加数据*/
insert into student(id,student_name,student_sex,student_number,school) values (1,'浅夏','女',2147483647,'南方大学'),(2,'樟道','男',2147483647,'北方大学'),(3,'苏熙','女',2147483647,'东方大学');
/*创建user表*/
CREATE TABLE user (
id int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
name varchar(10) COLLATE utf8_bin NOT NULL COMMENT '姓名',
password varchar(150) COLLATE utf8_bin DEFAULT NULL COMMENT '密码',
age int(10) NOT NULL COMMENT '年龄',
remark varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '备注',
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*向user表添加数据*/
insert into user(id,name,password,age,remark) values (1,'樟道','admin',21,'这是一个活泼的男孩'),(2,'浅夏','admin',19,'这是一个乐观的女孩'),(3,'苏熙','admin123',19,'这是一个可爱的美少女'),(4,'江芯','admin123',23,'这是一个丽江的姑娘');
student数据表的结构如下:
student数据表的内容如下:
user数据表的结构如下:
user数据表的内容如下:
二、Java项目
实现步骤
1、 在编写SQL时,如果返回的数据表字段名和对象属性名不一致,采取字段名后加别名(属性名)的形式编写SQL,例如:SELECTstudent_namestudentName,schoolFROM...
;
2、 先利用SQL进行查询,得到结果集(之前的查询部分还是没有问题的,只是对ResultSet结果集解析上的问题);
3、 利用反射创建目标实体类的对象(过程中要保证通用代码中不能出现目标对象的实例代码);
4、 获取结果集的列的别名:如SQL语句返回的字段名或别名(这里获取的优先别名);
5、 再获取结果集的每一列的值,结合第4步得到一个Map,键:列的别名,值:列的值;例如:studentName:浅夏
;
6、 再利用反射为第3步中目标对象对应的属性赋值,属性即为Map的键,值即为Map的值;
项目结构
- pom.xml:本项目采用maven管理依赖
- database.properties:存放数据库连接信息
- PropertiesUtils:读取properties文件的工具类
- ReflectionUtils:反射类工具类(核心工具类)(已被替代)
- User:用户实体类
- Student:学生实体类
- JDBCUtils:JDBC的工具类(核心类)
- JdbcObjectService:业务层接口
- JdbcObjectServiceImpl:业务层接口实现类
- TestJdbc:测试类,测试工具类的功能效果
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tqazy</groupId>
<artifactId>T-jdbc-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<build.sourceEncoding>UTF-8</build.sourceEncoding>
<mysql.version>5.1.26</mysql.version>
<spring.version>5.2.0.RELEASE</spring.version>
</properties>
<dependencies>
<!-- Spring start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring end -->
<!-- database start -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- database end -->
<!-- 测试 start -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 测试 end -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
</dependencies>
</project>
2. database.properties数据库连接信息文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
username=root
password=admin123
3. PropertiesUtils
前面几章多次讲解,这里就不赘述了
package com.tqazy.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* @author 散场前的温柔
*/
public class PropertiesUtils {
/**
* 传入properties文件地址和要读取的key值list集合,返回由key值和读取数据组成的map集合
*
* @param path
* @param list
* @return Map<key, value>
*/
public static Map<String, String> readProperties(String path, List<String> list) {
if (list == null || list.isEmpty() || path == null || path.isEmpty()) {
return new HashMap<String, String>(0);
}
Map<String, String> map = new HashMap<String, String>(10);
PropertiesUtils propertiesUtils = new PropertiesUtils();
Properties properties = propertiesUtils.readProperties(path);
if (properties == null) {
return new HashMap<String, String>(0);
}
for (String str : list) {
map.put(str, properties.getProperty(str));
}
return map;
}
/**
* 根据地址读取Properties文件并生成实例
*
* @param path 文件地址
* @return Properties
* @throws IOException
*/
private Properties readProperties(String path) {
Properties properties = null;
try {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path);
InputStreamReader reader = new InputStreamReader(inputStream);
// 此处需要使用JDK1.6以上的版本
properties = new Properties();
properties.load(reader);
} catch (NullPointerException e) {
// 读取文件异常
e.printStackTrace();
System.err.println("读取Properties文件异常,请检查文件路径和文件名:" + path);
} catch (IOException e) {
// 字符流写入异常
e.printStackTrace();
System.err.println("字符流写入Properties实例异常");
}
return properties;
}
}
4. ReflectionUtils
在开始分析之前,声明此类的方法已经找到了其他替代法,即使用apache组织提供的commons-beanutils的jar包里的BeanUtils.setProperty()方法,效果和用法和本类的方法效果一致,本类作为研究继续保留于此
这个比较难懂,这里我们先分析一下setFieldValueByParem()方法的参数:
1、 参数1:Objectobject这是被目标类反射的Object实例,例如:Objectobject=User.class.newInstance();
我们将要用object来代替目标类,实现属性值被赋值的操作;
2、 参数2:需要被赋值的对象属性名;
3、 参数3:需要赋值给目标对象属性名的属性值;
这个类的主要作用就是调用setFieldValueByParem()将属性值赋值到object中的属性名对应的属性上,这时的object其实就是目标类的反射(你可以理解为套着Object外皮的目标类)。到时我们再将object强制转化回目标类就可以了
至于具体的功能实现,代码里的注释足够详细,想要看懂,需要了解Object类、Class类的方法,建议查询JavaEE的API规范。
package com.tqazy.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author 散场前的温柔
*/
public abstract class ReflectionUtils {
private final static String SET = "set";
private final static String IS = "is";
/**
* 把属性和属性值赋值到反射后的Object中
*
* @param object 反射后的Object
* @param columnLable 属性名
* @param columnValue 属性值
*/
public static void setFieldValueByParem(Object object, String columnLable, Object columnValue) throws Exception{
// 将反射后的object取它的运行时类(即目标类)clazz
Class clazz = object.getClass();
// 把目标类的所有方法名取出形成数组methods
Method[] methods = clazz.getDeclaredMethods();
// 把目标类的所有属性名去除形成数组fields
Field[] fields = clazz.getDeclaredFields();
// 循环属性数组
for (Field field : fields) {
// 执行Field的getName()方法,取出属性名
String fieldName = field.getName();
// 指定Field的getType()方法,去取出类型的底层类简称
String fieldType = field.getType().getSimpleName();
// 如果类型是(boolean或Boolean)并且属性名以is开头的,属性名截去前两位。
if (("boolean".equals(fieldType) || "Boolean".equals(fieldType)) && IS.equals(fieldName.substring(0, 2))) {
fieldName = fieldName.substring(2);
}
// 如果解析出来的属性名和传入的属性名一直
if (fieldName.equals(columnLable)) {
// 生成属性名的set方法名
String fieldSetName = parGetOrSetName(fieldName, SET);
// 从目标类中获取需要的那个set方法
Method fieldSetMet = clazz.getMethod(fieldSetName, field.getType());
// 如果从方法名数组中找不到这个方法,那么就跳过
if (!checkMethod(methods, fieldSetMet)) {
continue;
}
// 如果传入的属性值不为空
if (columnValue != null) {
//通过找到的属性对应的set方法,把属性值赋值给object对象中,此时便完成了将属性值赋值给到反射后的object中
fieldSetMet.invoke(object, columnValue);
}
}
}
}
/**
* 生成属性名的set或get方法名
*
* @param fieldName 属性名
* @param met "set" or "get"
* @return
*/
private static String parGetOrSetName(String fieldName, String met) {
// 如果属性名为null或为空字符串,那么返回null
if (null == fieldName || "".equals(fieldName)) {
return null;
}
// 返回 方法名 = "set" + 属性名首字母大写 + 属性名除字母外的其他部分
return met + fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1);
}
/**
* 判断类是否存在该方法
*
* @param methods
* @param met
* @return
*/
private static boolean checkMethod(Method[] methods, Method met) {
// 如果传入的方法为空,那么返回false
if (met == null) {
return false;
}
// 如果传入的方法再方法数组中找到,那么返回true
for (Method method : methods) {
if (met.equals(method)) {
return true;
}
}
return false;
}
}
5. User实体类
package com.tqazy.entity;
/**
* @author 散场前的温柔
*/
public class User {
private Integer id;
private String name;
private String password;
private Integer age;
private String remark;
public User() {}
public User(String name, String password, Integer age, String remark) {
this.name = name;
this.password = password;
this.age = age;
this.remark = remark;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", age=" + age +
", remark='" + remark + '\'' +
'}';
}
}
6. Student实体类
package com.tqazy.entity;
/**
* @author 散场前的温柔
*/
public class Student {
private Integer id;
private String studentName;
private String studentSex;
private Integer studentNumber;
private String school;
public Student() {}
public Student(String studentName, String studentSex, Integer studentNumber, String school) {
this.studentName = studentName;
this.studentSex = studentSex;
this.studentNumber = studentNumber;
this.school = school;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public String getStudentSex() {
return studentSex;
}
public void setStudentSex(String studentSex) {
this.studentSex = studentSex;
}
public Integer getStudentNumber() {
return studentNumber;
}
public void setStudentNumber(Integer studentNumber) {
this.studentNumber = studentNumber;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", studentName='" + studentName + '\'' +
", studentSex='" + studentSex + '\'' +
", studentNumber=" + studentNumber +
", school='" + school + '\'' +
'}';
}
}
7. JDBCUtils工具类
package com.tqazy.jdbc;
import com.tqazy.utils.PropertiesUtils;
import com.tqazy.utils.ReflectionUtils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author 散场前的温柔
*/
public class JDBCUtils {
private static Connection con;
private static PreparedStatement ps;
private static ResultSet rs;
private static Map<String, String> map;
/**
* 获取数据库连接信息
*/
private static void getProperties(String path) {
List<String> list = new ArrayList<String>();
list.add("driver");
list.add("url");
list.add("username");
list.add("password");
map = PropertiesUtils.readProperties(path, list);
}
/**
* 获取数据库连接
*/
public static void getConnection(String path) {
try {
if (map == null) {
getProperties(path);
}
Class.forName(map.get("driver"));
con = DriverManager.getConnection(map.get("url"), map.get("username"), map.get("password"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static List<Object> select(String path, String sql, Class clazz, Object ... args){
getConnection(path);
// 如果创建连接失败,返回null
if(con == null){
System.out.println("创建数据库连接失败");
return null;
}
List<Object> list = new ArrayList<Object>();
try{
ps = con.prepareStatement(sql);
for(int i=0;i<args.length;i++){
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
// 本章内容这里开始
while(rs.next()){
// 1. 利用反射创建对象
Object obj = clazz.newInstance();
// 2. 通过解析SQL语句来判断到底选择了哪些列,以及需要为obj对象的哪些属性赋值
/** 2.1. 通过先获取ResultSet结果集的元数据对象ResultSetMetaData。
(关于ResultSetMetaData类在第10章《JDBC元数据》有讲解)
ResultSetMetaData可以获取结果集里的各种元素,比如:
getColumnLabel()方法可以获取指定列的别名;
getColumnCount()方法可以获取ResultSet对象中的列数 */
ResultSetMetaData rsmd = rs.getMetaData();
// 2.2 获取结果集的总数量并循环
for(int i=0;i<rsmd.getColumnCount();i++) {
// 获取指定列的结果的别名
String columnLabel = rsmd.getColumnLabel(i+1);
// 通过指定列的别名从结果集里获取结果值
Object columnValue = rs.getObject(columnLabel);
// 将列名和结果值赋值到object对象里
// 这里注释掉的是调用ReflectionUtils工具类的方法,因为这个方法的作用和Beanutils.setProperty()方法的用法和效果相同。
// 所以此处使用BeanUtils.setProperty()之后,第4个文件ReflectionUtils类就可以不用写了
// ReflectionUtils.setFieldValueByParem(obj, columnLabel, columnValue);
BeanUtils.setProperty(obj, columnLabel, columnValue);
}
// 将object放入集合里
list.add(obj);
}
} catch (Exception e){
e.printStackTrace();
} finally {
// 关闭数据库连接
close();
}
return list;
}
/**
* 关闭数据库相关连接,释放数据库资源
*/
public static void close() {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
8. JdbcObjectService接口
package com.tqazy.service;
import com.tqazy.entity.Student;
import com.tqazy.entity.User;
import java.util.List;
/**
* @author 散场前的温柔
*/
public interface TJdbcDemoService {
/**
* 查询用户列表
* @param name
* @return
*/
List<User> selectUserList(String name);
/**
* 查询学生列表
* @param name
* @return
*/
List<Student> selectStudentList(String name);
}
9. TJdbcDemoServiceImpl
package com.tqazy.service.impl;
import com.tqazy.entity.Student;
import com.tqazy.entity.User;
import com.tqazy.jdbc.JDBCUtils;
import com.tqazy.service.TJdbcDemoService;
import java.util.List;
/**
* @author 散场前的温柔
*/
public class TJdbcDemoServiceImpl implements TJdbcDemoService {
public List<User> selectUserList(String name) {
// 写SQL语句时,如果数据库字段名和对象属性名一致,则直接使用即可
String sql = "SELECT name, password, age, remark FROM user WHERE name = ?";
List<Object> list = JDBCUtils.select("database.properties", sql, User.class, name);
// 将返回的集合中的Object强转为目标类
List<User> userList = (List<User>)(List)list;
return userList;
}
public List<Student> selectStudentList(String name) {
// 写SQL语句时,如果数据库字段名和对象属性名不一致,那么需要在查询出的不一致的字段后面写上别名(此处别名就是对象属性名),例如: student_name studentName
String sql = "SELECT student_name studentName, student_sex studentSex, student_number studentNumber, school FROM student WHERE student_name = ?";
List<Object> list = JDBCUtils.select("database.properties", sql, Student.class, name);
List<Student> studentList = (List<Student>)(List)list;
return studentList;
}
}
10. TestJdbc测试类
我们这里调用两个表对应两个对象,使用同一个JDBCUtils工具类里select()方法,为了验证通用性
package com.tqazy.test;
import com.tqazy.entity.Student;
import com.tqazy.entity.User;
import com.tqazy.service.TJdbcDemoService;
import com.tqazy.service.impl.TJdbcDemoServiceImpl;
import org.junit.Test;
import java.util.List;
/**
* @author 散场前的温柔
*/
public class TestJdbc {
@Test
public void testJdbc(){
TJdbcDemoService service = new TJdbcDemoServiceImpl();
List<User> list = service.selectUserList("苏熙");
if (list == null) {
System.out.println("用户不存在");
} else {
for (User user : list) {
System.out.println(user.toString());
}
}
List<Student> list2 = service.selectStudentList("苏熙");
if (list2 == null) {
System.out.println("学生不存在");
} else {
for (Student student : list2) {
System.out.println(student.toString());
}
}
}
}
运行结果
分析
我们可以看到结果就是完成了我们预期的需求,其中解耦合,ServiceImpl调用JDBCUtils方法时,JDBCUtils类里没有其他的类的代码,且调用层无需解析JDBCUtils的代码,只需要强转一下即可,也完成了调用层无需过多解析的需求。
我们在代码里查询了两个表的数据,使用的时同一个select()的方法,所以此方法具备通用性;且返回的不在是原来的ResultSet结果集,而是返回的Object对象,又满足了面向对象的需求。
所以我们使用映射完成了通用的查询方法
本章结束
如果本文有错误或对本文有不理解的地方欢迎评论 _
如果本文有帮助到您,可以点一下右上角的赞哦,谢谢啦