大家好,我是一个爱举铁的程序员Shr。
本篇文章将用到前几篇文章介绍过的知识自定义数据访问层框架,建议看这篇文章之前先去了解JDBC元数据和反射。
如果是初学者,觉得JDBC封装数据太麻烦,一个类十多个字段,重复的代码导致浪费了大量时间,那待会我开车的时候你可要抓紧了。
如果你用Hibernate,Mybatis用了两三年还只是停留在使用的情况,看源码太费劲,看一会就想睡觉,本篇文章将带你走进框架底层,探索精彩的世界。
本篇文章较长,耐心看完~~~
源码地址:https://github.com/ShrMus/Dao/tree/master/dao_20180603/src/main/java/com/shrmus/jdbc02
新建名为dao_20180603的数据库,再新建emp表
CREATE TABLE `emp` (
`id` int(11) NOT NULL,
`name` varchar(255) default NULL,
`address` varchar(255) default NULL,
`hireDate` datetime default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
public class Emp {
private int id;
private String name;
private String address;
private Date hireDate;
public Emp() {
}
public Emp(int id, String name, String address, Date hireDate) {
this.id = id;
this.name = name;
this.address = address;
this.hireDate = hireDate;
}
@Override
public String toString() {
return "Emp [id=" + id + ", name=" + name + ", address=" + address + ", hireDate=" + hireDate + "]";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Date getHireDate() {
return hireDate;
}
public void setHireDate(Date hireDate) {
this.hireDate = hireDate;
}
}
/**
* 自定义数据源
* Title:MyDataSource
* Description:
* @author Shr
* @date 2018年6月3日上午9:32:51
* @version
*/
public class MyDataSource {
// 数据库驱动
private final String driverClassName;
// 数据库连接URL
private final String url;
// 数据库用户名
private final String username;
// 数据库密码
private final String password;
/**
* 构造方法注入属性值
*/
public MyDataSource(){
Properties properties = new Properties();
InputStream inputStream;
try {
String path = this.getClass().getResource("").getPath();
inputStream = new FileInputStream(path + "/jdbc.properties");
properties.load(inputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
this.driverClassName = properties.getProperty("jdbc.driverClassName");
this.url = properties.getProperty("jdbc.url");
this.username = properties.getProperty("jdbc.username");
this.password = properties.getProperty("jdbc.password");
}
public MyDataSource(String driverClassName,String url,String username,String password){
this.driverClassName = driverClassName;
this.url = url;
this.username = username;
this.password = password;
}
public String getDriverClassName() {
return driverClassName;
}
public String getUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
新建文件jdbc.properties,文件和数据源类在同一个目录,文件内容如下:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/dao_20180603?characterEncoding=utf8
jdbc.username=root
jdbc.password=shrmus
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* 自定义连接
* Title:MyConnection
* Description:
* @author Shr
* @date 2018年6月3日上午9:59:31
* @version
*/
public final class MyConnection{
private Connection connection = null;
public MyConnection() {
}
public MyConnection(MyDataSource myDataSource) {
try {
Class.forName(myDataSource.getDriverClassName());
connection = DriverManager.getConnection(myDataSource.getUrl(), myDataSource.getUsername(), myDataSource.getPassword());
} catch (ClassNotFoundException e) {
System.err.println("构造MyConnection实例失败!");
} catch (SQLException e) {
System.err.println("获取MyConnection失败!");
}
}
public Connection getConnection() {
return connection;
}
/**
* 关闭数据库连接对象
* @param connection
*/
public static void close(Connection connection) {
if(null != connection) {
try {
connection.close();
} catch (SQLException e) {
System.err.println("关闭数据库连接对象失败!");
}
}
}
/**
* 关闭SQL执行对象
* @param statement
*/
public static void close(Statement statement) {
if(null != statement) {
try {
statement.close();
} catch (SQLException e) {
System.err.println("关闭SQL执行对象失败!");
}
}
}
/**
* 关闭结果集对象
* @param resultSet
*/
public static void close(ResultSet resultSet) {
if(null != resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
System.err.println("关闭结果集对象失败!");
}
}
}
/**
* 关闭数据库连接对象,SQL执行对象,结果集对象
* @param resultSet
* @param statement
* @param connection
*/
public static void close(ResultSet resultSet, Statement statement, Connection connection){
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
System.err.println("关闭结果集对象失败!");
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
System.err.println("关闭SQL执行对象失败!");
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
System.err.println("关闭数据库连接对象失败!");
}
}
}
}
这个类用来对数据进行增删改查。
/**
* 自定义SQL执行对象
* Title:MyQuery
* Description:
* @author Shr
* @date 2018年6月3日上午10:18:11
* @version
*/
public class MyQuery {
// 数据库连接对象
public Connection connection;
public MyQuery() {
MyDataSource myDataSource = new MyDataSource();
MyConnection myConnection = new MyConnection(myDataSource);
this.connection = myConnection.getConnection();
}
public MyQuery(MyDataSource myDataSource) {
MyConnection myConnection = new MyConnection(myDataSource);
this.connection = myConnection.getConnection();
}
}
看到这里,你已经发现和原先的JDBCUtil完全不一样了,为什么要这么做呢,为了做到功能单一。
后面关键的地方来了。
/**
* 插入一条记录
* @param object
*/
public void insert(Object object) {
PreparedStatement prepareStatement = null;
ResultSet resultSet = null;
Class extends Object> clazz = object.getClass();
// 获取简单类名,数据库表名和类名一致
String simpleName = clazz.getSimpleName();
// 获取字段
Field[] declaredFields = clazz.getDeclaredFields();
String sql = "insert into " + simpleName;
String fieldString = "(";
String valueString = "values(";
// 获取字段的个数
int length = declaredFields.length;
try {
for(int i = 0; i < length - 1; i++) {
// 私有字段设置允许访问
declaredFields[i].setAccessible(true);
// 获取字段值
Object fieldValue = declaredFields[i].get(object);
String typeName = declaredFields[i].getGenericType().getTypeName();
if(typeName.toLowerCase().contains("date")) {
// 如果是日期类型
Date date = (Date) fieldValue;
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = dateFormat.format(date);
fieldValue = format;
}
// 拼接字段名
fieldString += declaredFields[i].getName() + ",";
// 拼接字段值
valueString += "'" + fieldValue + "',";
}
declaredFields[length - 1].setAccessible(true);
// 获取字段值
Object fieldValue = declaredFields[length - 1].get(object);
// 获取字段类型名称
String typeName = declaredFields[length - 1].getGenericType().getTypeName();
if(typeName.toLowerCase().contains("date")) {
// 如果是日期类型
Date date = (Date) fieldValue;
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = dateFormat.format(date);
fieldValue = format;
}
// 拼接字段名
fieldString += declaredFields[length - 1].getName() + ") ";
// 拼接字段值
valueString += "'" + fieldValue + "')";
// 拼接SQL语句
sql = sql + fieldString + valueString;
System.out.println("SQL = " + sql);
// 设置事务手动提交
this.connection.setAutoCommit(false);
prepareStatement = this.connection.prepareStatement(sql);
prepareStatement.executeUpdate();
// 提交事务
this.connection.commit();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (SQLException e) {
try {
// 回滚事务
this.connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
// 关闭连接
MyConnection.close(resultSet, prepareStatement, connection);
}
}
public class EmpDao {
/**
* 添加员工的信息
*/
public void insert(Emp emp) {
MyQuery myQuery = new MyQuery();
myQuery.insert(emp);
}
}
public class EmpTest {
/**
* 测试添加
*/
@Test
public void inser() throws Exception{
Emp emp = new Emp(2,"李四","株洲",new Date());
EmpDao empDao = new EmpDao();
empDao.insert(emp);
}
}
运行结果:
SQL = insert into Emp(id,name,address,hireDate) values('1','张三','长沙','2018-06-03 23:49:12')
再看数据库:
添加了一条数据。查询类中的inser()方法根据传过来的参数,获取数据库表的主键,再获取字段和字段值,方法内部拼接SQL语句。
/**
* 查询所有信息
* @param t
* @return
*/
@SuppressWarnings({ "unchecked", "resource" })
public List select(Class t){
PreparedStatement prepareStatement = null;
ResultSet resultSet = null;
List objectList = new ArrayList<>();
// 获取模板T的实例
T newInstance2 = null;
try {
newInstance2 = t.newInstance();
} catch (InstantiationException | IllegalAccessException e1) {
e1.printStackTrace();
}
T newInstance;
// 获取实例的Class类对象
Class extends Object> clazz = newInstance2.getClass();
// 获取简单类名,数据库表名和类名一致
String simpleName = clazz.getSimpleName();
try {
// 获取数据库元数据
DatabaseMetaData databaseMetaData = connection.getMetaData();
// 获取表列
resultSet = databaseMetaData.getColumns(null, null, simpleName, null);
List columnNameList = new ArrayList<>();
while(resultSet.next()) {
// 获取列名
String columnName = resultSet.getString("COLUMN_NAME");
columnNameList.add(columnName);
}
// 定义SQL语句
String sql = "select ";
for(String columnName : columnNameList) {
sql += columnName + ",";
}
// 删除最后一个逗号
int lastIndexOf = sql.lastIndexOf(",");
sql = sql.substring(0, lastIndexOf);
sql += " from " + simpleName;
System.out.println("SQL = " + sql);
prepareStatement = this.connection.prepareStatement(sql);
// 执行查询
resultSet = prepareStatement.executeQuery();
while(resultSet.next()) {
// 创建一个新的实例
newInstance = (T) clazz.newInstance();
// 获取结果集元数据
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
// 获取查询出来的总列数
int columnCount = resultSetMetaData.getColumnCount();
for(int i = 0; i < columnCount; i++) {
// 获取列名
String columnName = resultSetMetaData.getColumnName(i + 1);
// 获取列值
Object fieldValue = resultSet.getObject(columnName);
// 根据列名获取字段
Field declaredField = clazz.getDeclaredField(columnName);
// 私有字段设置允许访问
declaredField.setAccessible(true);
// 调用方法给字段赋新的值
declaredField.set(newInstance, fieldValue);
}
objectList.add(newInstance);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally {
// 关闭连接
MyConnection.close(resultSet, prepareStatement, connection);
}
return objectList;
}
7.2.2 编写Dao类
/**
* 查询所有员工
*/
public List getEmpList() {
MyQuery myQuery = new MyQuery();
List select = myQuery.select(Emp.class);
return select;
}
/**
* 测试查询所有员工
*/
@Test
public void getEmpList() throws Exception {
EmpDao empDao = new EmpDao();
List empList = empDao.getEmpList();
for(Emp emp : empList) {
System.out.println(emp);
}
}
在运行这个测试方法之前我又添加了一条数据。
运行结果:
SQL = select id,name,address,hireDate from Emp
Emp [id=1, name=张三, address=长沙, hireDate=2018-06-03 23:49:12.0]
Emp [id=2, name=李四, address=株洲, hireDate=2018-06-04 00:00:18.0]
/**
* 根据主键查询
* @param t
* @return
*/
@SuppressWarnings({ "unchecked", "resource" })
public T selectByPrimaryKey(T t){
PreparedStatement prepareStatement = null;
ResultSet resultSet = null;
Class extends Object> clazz = t.getClass();
// 获取简单类名,数据库表名和类名一致
String simpleName = clazz.getSimpleName();
T newInstance = null;
try {
// 创建一个新的实例
newInstance = (T) clazz.newInstance();
// 获取数据库元数据
DatabaseMetaData databaseMetaData = connection.getMetaData();
// 获取给定表主键列的描述
resultSet = databaseMetaData.getPrimaryKeys(null, null, simpleName);
String primaryKey = null;
if(resultSet.next()) {
// 获取主键列的名称
primaryKey = resultSet.getString("COLUMN_NAME");
}
// 获取表列
resultSet = databaseMetaData.getColumns(null, null, simpleName, null);
List columnNameList = new ArrayList<>();
while(resultSet.next()) {
// 获取列名
String columnName = resultSet.getString("COLUMN_NAME");
columnNameList.add(columnName);
}
if(null != primaryKey) {
// 获取主键在实体类中的同名字段
Field declaredField = clazz.getDeclaredField(primaryKey);
// 设置私有字段允许访问
declaredField.setAccessible(true);
// 获取字段的值
Object primaryKeyValue = declaredField.get(t);
// 获取所有字段
Field[] declaredFields = clazz.getDeclaredFields();
// 定义SQL语句
String sql = "select ";
for(String columnName : columnNameList) {
sql += columnName + ",";
}
// 删除最后一个逗号
int lastIndexOf = sql.lastIndexOf(",");
sql = sql.substring(0, lastIndexOf);
sql += " from " + simpleName + " where " + primaryKey + "='" + primaryKeyValue + "'";
System.out.println("SQL = " + sql);
prepareStatement = connection.prepareStatement(sql);
resultSet = prepareStatement.executeQuery();
if(resultSet.next()) {
// 获取结果集元数据
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
// 获取查找出来的总列数
int columnCount = resultSetMetaData.getColumnCount();
for (int i = 0; i < columnCount; i++) {
// 获取列名
String columnName = resultSetMetaData.getColumnName(i + 1);
// 获取列值
Object columnValue = resultSet.getObject(columnName);
for(Field field : declaredFields) {
field.setAccessible(true);
// 属性名和字段名一样
if(field.getName().equals(columnName)){
// 设置属性值
field.set(newInstance, columnValue);
}
}
}
}
} else {
System.err.println("主键为空!");
}
} catch (SQLException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
MyConnection.close(resultSet, prepareStatement, connection);
}
return newInstance;
}
/**
* 根据主键查询员工的信息
*/
public Emp selectByPrimaryKey(Emp emp) {
MyQuery myQuery = new MyQuery();
emp = myQuery.selectByPrimaryKey(emp);
return emp;
}
/**
* 测试根据主键查询
*/
@Test
public void selectByPrimaryKey() throws Exception {
EmpDao empDao = new EmpDao();
Emp emp = new Emp();
emp.setId(1);
emp = empDao.selectByPrimaryKey(emp);
System.out.println(emp);
}
运行结果:
SQL = select id,name,address,hireDate from Emp where id='1'
Emp [id=1, name=张三, address=长沙, hireDate=2018-06-03 23:49:12.0]
/**
* 根据主键修改记录
* @param object
*/
public void updateByPrimaryKey(Object object) {
PreparedStatement prepareStatement = null;
ResultSet resultSet = null;
Class extends Object> clazz = object.getClass();
// 获取简单类名,数据库表名和类名一致
String simpleName = clazz.getSimpleName();
try {
// 获取数据库元数据
DatabaseMetaData databaseMetaData = connection.getMetaData();
// 获取给定表主键列的描述
resultSet = databaseMetaData.getPrimaryKeys(null, null, simpleName);
String primaryKey = null;
if(resultSet.next()) {
// 获取主键列的名称
primaryKey = resultSet.getString("COLUMN_NAME");
}
if(null != primaryKey) {
// 获取主键在实体类中的同名字段
Field declaredField = clazz.getDeclaredField(primaryKey);
// 设置私有字段允许访问
declaredField.setAccessible(true);
// 获取字段的值
Object primaryKeyValue = declaredField.get(object);
// 获取字段
Field[] declaredFields = clazz.getDeclaredFields();
// 定义SQL语句
String sql = "update " + simpleName + " set ";
for(Field field : declaredFields) {
String fieldName = field.getName();
// 这个字段不是主键
if(!fieldName.equals(primaryKey)) {
// 设置允许访问
field.setAccessible(true);
sql += fieldName + "='";
// 获取属性值
Object fieldValue = field.get(object);
// 获取字段类型名称
String typeName = field.getGenericType().getTypeName();
// 不是基本数据类型
if(typeName.toLowerCase().contains("date")) {
// 如果是日期类型
Date date = (Date) fieldValue;
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = dateFormat.format(date);
sql += format + "', ";
}else {
sql += fieldValue + "', ";
}
}
}
// 删除最后一个逗号
int lastIndexOf = sql.lastIndexOf(",");
sql = sql.substring(0, lastIndexOf);
sql += " where " + primaryKey + "='" + primaryKeyValue + "' ";
System.out.println("SQL = " + sql);
// 开启事务
connection.setAutoCommit(false);
prepareStatement = connection.prepareStatement(sql);
prepareStatement.executeUpdate();
// 提交事务
connection.commit();
} else {
System.err.println("主键为空!");
}
} catch (SQLException e) {
try {
// 事务回滚
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally {
MyConnection.close(resultSet, prepareStatement, connection);
}
}
/**
* 根据主键修改员工的信息
*/
public void updateByPrimaryKey(Emp emp) {
MyQuery myQuery = new MyQuery();
myQuery.updateByPrimaryKey(emp);
}
/**
* 测试修改
*/
@Test
public void updateByPrimaryKey() throws Exception {
Emp emp = new Emp(1,"王五","广州",new Date());
EmpDao empDao = new EmpDao();
empDao.updateByPrimaryKey(emp);
}
运行结果:
SQL = update Emp set name='王五', address='广州', hireDate='2018-06-04 00:05:55' where id='1'
看看数据库:
修改成功了。激动人心吧。以前怎么没发现JDBC元数据这么好使呢。
/**
* 根据主键删除记录
* @param object
*/
public void deleteByPrimaryKey(Object object) {
PreparedStatement prepareStatement = null;
ResultSet resultSet = null;
Class extends Object> clazz = object.getClass();
// 获取简单类名,数据库表名和类名一致
String simpleName = clazz.getSimpleName();
try {
// 获取数据库元数据
DatabaseMetaData databaseMetaData = connection.getMetaData();
// 获取给定表主键列的描述
resultSet = databaseMetaData.getPrimaryKeys(null, null, simpleName);
String primaryKey = null;
if(resultSet.next()) {
// 获取主键列的名称
primaryKey = resultSet.getString("COLUMN_NAME");
}
if(null != primaryKey) {
// 获取主键在实体类中的同名字段
Field declaredField = clazz.getDeclaredField(primaryKey);
// 设置私有字段允许访问
declaredField.setAccessible(true);
// 获取字段的值
Object primaryKeyValue = declaredField.get(object);
// 定义SQL语句
String sql = "delete from " + simpleName + " where " + primaryKey + "='" + primaryKeyValue + "'";
System.out.println("SQL = " + sql);
// 开启事务
connection.setAutoCommit(false);
prepareStatement = connection.prepareStatement(sql);
prepareStatement.executeUpdate();
connection.commit();
} else {
System.err.println("主键为空!");
}
} catch (SQLException e) {
try {
// 事务回滚
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally {
MyConnection.close(resultSet, prepareStatement, connection);
}
}
/**
* 根据主键删除员工的信息
*/
public void deleteByPrimaryKey(Emp emp) {
MyQuery myQuery = new MyQuery();
myQuery.deleteByPrimaryKey(emp);
}
/**
* 测试删除
*/
@Test
public void deleteByPrimaryKey() throws Exception {
Emp emp = new Emp(1,"李四","广州",new Date());
EmpDao empDao = new EmpDao();
empDao.deleteByPrimaryKey(emp);
}
运行结果:
SQL = delete from Emp where id='1'
再看看数据库:
因为是根据主键删除的,所以测试类的员工的名字是李四就忽略了。
如果你仔细看了查询类的代码或者你自己有练习过,你就会发现那些框架是怎样的思路了。XML,注解,映射就都能拨云见日了。以前在Dao类作死地封装数据,现在就轻松多了。
注意事项:
我写的都是基本的增删改查,没有涉及到多表查询的。
目前只支持MySQL
只支持表名和类名一样
只支持表中字段和类的属性名一样
不支持联合主键
未预防SQL注入
未提供连接池
关于JDBC元数据和反射的知识点我就不多说了,可能看不懂的也看不到底下了,如果有疑问可以给我留言。