简介
好了,其实就目前来说,我们的JDBC类已经封装的差不多了。但是!别忘了我们最初的目的!手写极简版的小框架!!
所以对于目前来说,我们的数据库连接的管理(即:啥时候连接,啥时候断开,连接时长等)在我们的工具类中还是很混乱的,而站在巨人的肩膀上才能看的更远!
SO,我们选择使用连接池进行自动管理。当然,市面上连接池有不少,我们选用来自阿里的德鲁伊(druid)来进行管理。别问为啥,我就是觉得这个名字挺不错的,想用别的也行,没啥大区别。
之后,我们还需要加入动态代理,来让我们的项目更偏向于一个”框架“。
配置连接池
我们的项目使用的是Mevn进行管理,所以嘛,,在pom.xml中加入包就可以啦:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
然后在我们的resources目录下创建配置文件,文件名以.properties结尾:
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test
#&useSSL=false
username=root
password=123456
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
好了,基本配置完成,我们先来使用下叭:
Properties properties = new Properties();
properties.load(MysqlUtil.class.getClassLoader().getResourceAsStream(configName));
dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("select * from User");
ResultSet resultSet = preparedStatement.executeQuery();
(configName是你的文件名,上篇里面有详细解释)
可以看出,德鲁伊这个包采用的是工厂设计模式,嘿嘿,这次我们先不用这种设计模式,后面再慢慢引入。
芜湖~真是太方便了。。。
接下来,我们把它好好的封装起来
工具封装
这里,我新建一个MysqlUtil的类,在里面进行德鲁伊的封装:
package utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author :byMJC
* @date :Created 2022/1/16 19:38
* @description:
*/
public class MysqlUtil {
private static DataSource dataSource;
public MysqlUtil(String s) {
try {
parseConfig(s);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 解析配置文件
* @param configName 文件名
* @throws IOException
*/
public void parseConfig(String configName) throws Exception {
Properties properties = new Properties();
properties.load(MysqlUtil.class.getClassLoader().getResourceAsStream(configName));
dataSource = DruidDataSourceFactory.createDataSource(properties);
}
/**
* 获取一个连接
* @return
*/
public Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
}
这样,我们使用
MysqlUtil mysqlUtil = new MysqlUtil("JDBCConfig.properties");
mysqlUtil.getConnection();
就可以拿到我们的连接了。
java动态代理
一般动态代理有俩种,java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
我们选用jva动态代理来实现!
具体步骤:
- 通过实现 InvocationHandler 接口创建自己的调用处理器
- 通过为 Proxy 类指定 ClassLoader对象和一组interface来创建动态代理类
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
好了,加下来我们来实现下试试叭:
首先创建一个User接口和一个User的实现类在entity包下:
代码内容嘛,就定义了一个say函数,打印了点东西:
然后,再handler包下创建一个UserInvocationHandler类,实现InvocationHandler接口(即上面介绍的第一步,创建自己的处理器类):
package com.sy.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author :byMJC
* @date :Created 2022/1/17 0:23
* @description:
*/
public class UserInvocationHandler implements InvocationHandler {
// 用于接收传进来的要被代理的对象
private Object object;
public UserInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("代理:执行方法前!");
// 执行函数
method.invoke(object,objects);
System.out.println("代理:方法执行后!");
return null;
}
}
如果method.invoke(object,objects);
看不懂的化,可以再去看看反射。
,随后,我们就可以在主函数内实现一个简单的动态代理啦:
public static void main(String[] args) {
// 明明是动态代理了,为啥还要新建一个实体类类?
//只是方便拿这个接口的方法罢了,如果传入的方法不对,后面的强转会报错的。
//还有就是调用方法需要实体类,如果使用反射创建的话,
//,这个"简单的例子"就不那么"简单"了,哈哈
User user = new UserImpl();
/**
* 参数一:一个classLoader的对象,定义了由哪个classloader对象生成的代理类进行加载
* 参数二:一个接口数组,表示我们的代理对象有哪些接口
* 参数三:一个InbocationHander对象,表示动态代理对象会关联到哪个InvocationHander对象上,并有它调用。
* 返回值:返回代理对象
*/
User user1 = (User) Proxy.newProxyInstance(demo.class.getClassLoader(),
user.getClass().getInterfaces(), new UserInvocationHandler(user));
user1.say();
}
运行结果:
ok,ok,现在为止,你的动态代理就算会了,接下来我们来结合反射和注解,来融入我们的实战中叭!
加入动态代理
由于现在的项目有点点小大了,如果按顺序说反而更复杂,不如直接给大家看总体的比较直观。
项目结构
我们创建一个实体类User用于接收查询的数据,创建一个UserDao来进行对数据库User的查找(这里只列举查找)。
然后新建一个utils包,里面放我们的德鲁伊工具类和我们的SqlInvocationHandler类(sql语句的处理器,以及俩自己写的注解,这俩注解主要是后面用于存放sql语句,返回类型,数据库映射等信息):
当然,还有我们的主函数在demo中!
MysqlUtil实现
MysqlUtil主要是前面对德鲁伊的封装,现在再加上创建动态代理的函数,整体代码如下:
package utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author :byMJC
* @date :Created 2022/1/16 19:38
* @description:
*/
public class MysqlUtil {
private static DataSource dataSource;
public MysqlUtil(String s) {
try {
parseConfig(s);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 解析配置文件
* @param configName 文件名
* @throws IOException
*/
public void parseConfig(String configName) throws Exception {
Properties properties = new Properties();
properties.load(MysqlUtil.class.getClassLoader().getResourceAsStream(configName));
dataSource = DruidDataSourceFactory.createDataSource(properties);
}
/**
* 获取一个连接
* @return
*/
public Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
public <T> T getMapper(Class clazz){
/**
* 参数一:一个classLoader的对象,定义了由哪个classloader对象生成的代理类进行加载
* 参数二:一个接口数组,表示我们的代理对象有哪些接口
* 参数三:一个InbocationHander对象,表示动态代理对象会关联到哪个InvocationHander对象上,并有它调用。
* 返回值:返回代理对象
*/
T t = (T)Proxy.newProxyInstance(MysqlUtil.class.getClassLoader(),
new Class[] {clazz}, new SqlInvocationHandler(getConnection(),clazz));
return t;
}
}
这里我们可以看到,我们创建代理的函数有一个传入参数,是一个Class,它就是我们要被代理的对象的类型,嘿嘿,因为前面的例子为了更直观和简单,所以没有用反射,但是现在实战了。
而我们的SqlInvocationHandler的构造函数也需要connecion来进行数据库操作,需要被代理对象的Class后面用于获取对应方法。
DataName注解和SqlString注解的实现
DataName注解主要用于实体类对象对数据库的映射,因为为了防止sql注入,实体类的成员的名字在代码中和数据库中往往不会取一模一样的。
// 注解类型是使用在成员上,生效时间是运行时
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataName {
String value();
}
,SqlString注解用于存放sql语句,sql数据类型和返回数据类型:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SqlString {
String sql();
String type();
String resType();
}
SqlInvocationHandler类实现
这里面主要是查询数据,然后利用反射来返回数据了。
package utils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
/**
* @author :byMJC
* @date :Created 2022/1/16 21:21
* @description:
*/
public class SqlInvocationHandler implements InvocationHandler {
public Connection connection;
public Class aClass;
public SqlInvocationHandler(Connection connection,Class clazz) {
this.connection = connection;
this.aClass = clazz;
}
/**
* 获取这个类的方法的注解
* @param method
* @return
*/
public SqlString getSqlString(Method method){
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.getName().equals(method.getName())){
return declaredMethod.getAnnotation(SqlString.class);
}
}
System.out.println("注解出错!");
return null;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
List resList = new ArrayList();
// 获取该方法的注解
SqlString sqlString = getSqlString(method);
if(sqlString.type().equals("select")){
// 利用反射获取返回类型的class
Class<?> tClass = Class.forName(sqlString.resType());
Field[] declaredFields = tClass.getDeclaredFields();
// 创建语句
PreparedStatement preparedStatement = connection.prepareStatement(sqlString.sql());
// 赋值参数
for (int i = 0; i < objects.length; i++) {
preparedStatement.setObject(i+1, objects[i]);
// System.out.println(objects[i]);
}
// 执行语句
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
// 反射构建类
Object res = tClass.getDeclaredConstructor().newInstance();
for (Field declaredField : declaredFields) {
Object object = resultSet.getObject(declaredField.getAnnotation(DataName.class).value());
declaredField.setAccessible(true);
declaredField.set(res, object);
}
resList.add(res);
}
preparedStatement.close();
resultSet.close();
return resList;
}
return null;
}
}
总体思路就是利用反射获取我们的注解的数据,然后据此进行sql类型匹配。后面就和我们前面几篇用反射封装JDBC的代码差不多了。
代码运行
最后附上我们的UserDao、User、和数据库的测试内容
UserDao:
package dao;
import entity.User;
import utils.SqlString;
import java.util.List;
/**
* @author :byMJC
* @date :Created 2022/1/16 21:01
* @description:
*/
public interface UserDao {
@SqlString(sql = "select * from user where user_id = ?", type = "select",resType = "entity.User")
List<User> getUserById(Integer id);
}
User:
package entity;
import utils.DataName;
/**
* @author :byMJC
* @date :Created 2022/1/16 19:36
* @description:
*/
public class User {
@DataName("user_id")
private int id;
@DataName("user_name")
private String userName;
@DataName("user_pwd")
private String userPwd;
@DataName("user_Realname")
private String userRealname;
@DataName("user_tel")
private String userTel;
public User() {
}
public User(int id, String userName, String userPwd, String userRealname, String userTel) {
this.id = id;
this.userName = userName;
this.userPwd = userPwd;
this.userRealname = userRealname;
this.userTel = userTel;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPwd() {
return userPwd;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
public String getUserRealname() {
return userRealname;
}
public void setUserRealname(String userRealname) {
this.userRealname = userRealname;
}
public String getUserTel() {
return userTel;
}
public void setUserTel(String userTel) {
this.userTel = userTel;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", userPwd='" + userPwd + '\'' +
", userRealname='" + userRealname + '\'' +
", userTel='" + userTel + '\'' +
'}';
}
}
数据库数据:
嘿嘿:主函数:
public static void main(String[] args) throws Exception {
MysqlUtil mysqlUtil = new MysqlUtil("JDBCConfig.properties");
UserDao Proxy = mysqlUtil.getMapper(UserDao.class);
List u = (List)Proxy.getUserById(1);
for (Object o : u) {
System.out.println(o);
}
}
运行结果:
嘿嘿,怎么样,看着我们的Dao层和实体类的代码,以及最后的效果,是不是有那么一点点点使用框架的味道了?
当然,整个项目不论是结构还是使用逻辑都还是比较粗糙的,下一篇会使用工厂设计模式把项目整体的重构一下。