创建一个新的模块 spring6-009-jdbc
导入我们需要的依赖 + 打Jar包
<packaging>jar</packaging>
<!--配置多个仓库repositories标签-->
<repositories>
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<!--依赖配置 spring-context、mysql数据驱动、junit单元测试、jdbc相关依赖-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!--使用德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
</dependencies>
在数据库中创建一张表 t_user,表的结构如下:【可以初始化几条数据】

我们创建一个pojo类 com.powernode.spring.bean.User 用来封装表中字段
package com.powernode.spring6.bean;
/**
* pojo类对应我们的 t_user 表
* @author Bonbons
* @version 1.0
*/
public class User {
private Integer id;
private String realName;
private Integer age;
public User() {
}
public User(Integer id, String realName, Integer age) {
this.id = id;
this.realName = realName;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", realName='" + realName + '\'' +
", age=" + age +
'}';
}
}
这个JdbcTemplate 有一个属性,dataSource,所以我们还需要给他提供数据源的 Bean
我们可以自己定义一个数据源【只要实现了DataSource接口的类都可以充当数据源】
package com.powernode.spring6.bean;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* @author Bonbons
* @version 1.0
*/
public class MyDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password;
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
@Override
public Connection getConnection() throws SQLException {
try {
// 注册驱动
Class.forName(driver);
// 返回创建的连接对象
return DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
// 这里有个 return null; 被我注释掉了
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
我们需要将 JdbcTemplate 和 我们自定义的数据源在配置文件中声明一下,交给Spring容器管理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--JDBC模板类放到Spring容器中-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="myDataSource" />
</bean>
<!--配置我们自己定义的数据源 >> 只要实现了DataSource接口我们都可以称之为数据源-->
<bean id="myDataSource" class="com.powernode.spring6.bean.MyDataSource">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="111111"/>
</bean>
</beans>
我们在测试方法中通过getBean获取到 JdbcTemplate 的对象,然后就可以调用方法操作数据库了
所以下面演示那些功能,直接通过测试方法实现
对于功能的解释我都写在了注释里面
@Test
public void testJdbc() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 成功获得jdbcTemplate的对象,之后就可以用这个对象操作数据库
System.out.println(jdbcTemplate);
}
通过运行结果,我们可以知道 JDBCTemplate 可以成功托管到Spring中,接下来我们就可以调用它的方法了

@Test
public void testInsert(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 现在我们想插入数据,insert语句
String sql = "insert into t_user (real_name, age) values (?,?)";
// 在jdbcTemplate中,增删改都使用 update 方法
int count = jdbcTemplate.update(sql, "吃不饱三战士", 25);
System.out.println(count);
}
@Test
public void testUpdate(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 根据id更新我们数据库表中的数据
String sql = "update t_user set real_name = ?, age = ? where id = ?";
// 调用我们的update方法,传递sql语句以及与我们 ? 对应的参数值
int count = jdbcTemplate.update(sql, "哪吒三太子", 15, 1);
System.out.println(count);
}
@Test
public void testDelete(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 删除SQL
String sql = "delete from t_user where id = ?";
// 调用我们的 update 方法
jdbcTemplate.update(sql, 2);
}

@Test
public void testQueryOne(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 根据id查询一条记录
String sql = "select id, real_name, age from t_user where id = ?";
// 查询语句调用的是 QueryForObject 方法,参数:sql语句、new BeanPropertyRowMapper<>(类型)查询结果映射对象、?参数传值
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);
System.out.println(user);
}

@Test
public void testQueryAll(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 查询数据库表中的全部数据
String sql = "select id, real_name, age from t_user";
// 查询全部数据我们调用的是 query 方法
List<User> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
System.out.println(query);
}

@Test
public void testQueryOneValue(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 查询一个值 >> 此处以查询数据库表中记录条数为例[count(具体的列)只会本列不为空的记录条数]
String sql = "select count(*) from t_user";
// 一条记录,我们调用的还是 queryForObject
Integer count = jdbcTemplate.queryForObject(sql, int.class);
System.out.println(count);
}

@Test
public void testBatchInsert(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 插入数据的SQL
String sql = "insert into t_user (real_name, age) value(?, ?)";
// 利用数组封装我们要插入的一条数据
Object [] obj1 = {"张三", 22};
Object [] obj2 = {"李四", 25};
Object [] obj3 = {"王五", 30};
// 利用一个集合对象存储这些待插入的数据
List<Object[]> list = new ArrayList<>();
list.add(obj1);
list.add(obj2);
list.add(obj3);
// 调用我们的 batchUpdate 方法插入多条数据,参数1为sql、参数2为集合对象
int[] counts = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(counts));
}

@Test
public void testBatchUpdate(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 插入数据的SQL
String sql = "update t_user set real_name = ?, age = ? where id = ?";
// 利用数组封装我们要更新的数据和对应的id
Object [] obj1 = {"杨戬", 23, 4};
Object [] obj2 = {"李靖", 25, 5};
Object [] obj3 = {"孙悟空", 22, 6};
// 利用一个集合对象存储数据
List<Object[]> list = new ArrayList<>();
list.add(obj1);
list.add(obj2);
list.add(obj3);
// 调用我们的 batchUpdate 方法更新多条数据,参数1为sql、参数2为集合对象
int[] counts = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(counts));
}

@Test
public void testBeachDelete(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 删除语句
String sql = "delete from t_user where id = ?";
// 准备数据,只有一项也得用数组
Object [] obj1 = {4};
Object [] obj2 = {5};
Object [] obj3 = {6};
// 利用一个集合对象存储数据
List<Object[]> list = new ArrayList<>();
list.add(obj1);
list.add(obj2);
list.add(obj3);
// 调用我们的 batchUpdate 方法删除多条数据,参数1为sql、参数2为集合对象
int[] counts = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(counts));
}

@Test
public void testCallback(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 编写根据id查询数据的SQL
String sql = "select id, real_name, age from t_user where id = ?";
// 注册回调函数,execute执行之后,会调用我们的doInPreparedStatement方法
User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
@Override
public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
User user = null;
// 参数代表对应第1个 ?, 参数值为3
ps.setInt(1, 3);
//
ResultSet rs = ps.executeQuery();
if (rs.next()) {
int id = rs.getInt("id");
String realName = rs.getString("real_name");
int age = rs.getInt("age");
// 将获取到的数据封装到我们的User对象中
user = new User(id, realName, age);
}
return user;
}
});
System.out.println(user);
}

<!--使用德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--JDBC模板类放到Spring容器中-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="myDataSource" />
</bean>
<!--配置德鲁伊连接池-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--此处这个driver的名字有变化-->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="111111"/>
</bean>
</beans>

用一个类代表另一个类的功能。属于GoF23种设计模式中的结构型模式
使用代理模式有什么用?
代理模式有哪些角色?
代理模式的实现方式?
本篇内容概述:我们结合实例来演示什么是静态代理,以及如何实现基于JDK的动态代理(重点内容),还有涉及如何使用CGLIB的动态代理
编写我们的 OrderService 接口
package com.powernode.proxy.service;
/**
* 代理机制的公共接口
* @author Bonbons
* @version 1.0
*/
public interface OrderService {
// 生成订单
void generate();
// 查看订单
void detail();
// 修改订单
void modify();
}
编写我们的接口实现类 OrderServiceImpl
package com.powernode.proxy.service;
/**
* 代理机制的目标对象
* @author Bonbons
* @version 1.0
*/
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("订单已生成");
}
@Override
public void detail() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("查看订单详情");
}
@Override
public void modify() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("订单已修改");
}
}
此处我们通过 Thread.sleep(number);线程睡眠来模拟实际操作的等待时间
编写测试程序,查看是否可以正常进行业务操作
public class Test {
public static void main(String[] args) {
// 创建实现类的对象
OrderService orderService = new OrderServiceImpl();
// 调用业务方法
orderService.generate();
orderService.detail();
orderoService.modify();
}
}

现在我们产生了新的需求:要计算每个方法的执行时长,那我们如何解决呢?
类和类之间的关系:【此处说明泛化关系和关联关系】

基于上面的接口和接口实现类,我们需要编写代理对象类 OrderServiceProxy 并实现接口
package com.powernode.proxy.service;
/**
* 代理对象
* @author Bonbons
* @version 1.0
*/
public class OrderServiceProxy implements OrderService{
// 我们将公共接口作为代理对象的属性 [关联关系--耦合度更低]
private OrderService target;
// 通过构造方法将目标对象传递给代理对象
public OrderServiceProxy(OrderService orderService) {
this.target = orderService;
}
@Override
public void generate() {
long start = System.currentTimeMillis();
target.generate();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - start)+"毫秒");
}
@Override
public void detail() {
long start = System.currentTimeMillis();
target.detail();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - start)+"毫秒");
}
@Override
public void modify() {
long start = System.currentTimeMillis();
target.modify();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - start)+"毫秒");
}
}
编写我们的测试程序
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService proxy = new OrderServiceProxy(target);
// 调用代理对象的方法
proxy.generate();
proxy.detail();
proxy.modify();


什么是动态代理?
在内存当中动态生成类的技术常见的包括:
编写修改后的接口,添加了一个拥有返回值的方法 getName()
package com.powernode.proxy.service;
/**
* 代理机制的公共接口
* @author Bonbons
* @version 1.0
*/
public interface OrderService {
// 生成订单
void generate();
// 查看订单
void detail();
// 修改订单
void modify();
// 为了研究invoke返回值问题,我们写一个有返回值的方法
String getName();
}
编写我们新的实现类
package com.powernode.proxy.service;
/**
* 代理机制的目标对象
* @author Bonbons
* @version 1.0
*/
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("订单已生成");
}
@Override
public void detail() {
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("查看订单详情");
}
@Override
public void modify() {
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("订单已修改");
}
@Override
public String getName() {
System.out.println("getName方法执行!");
return "Bonbons";
}
}
编写我们的调用处理器 TimeInvocationHandler,负责调用我们目标对象的目标方法
package com.powernode.proxy.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 关于计时的调用处理器对象,不需要重复编写,在内部编写与计时相关的代码
* @author Bonbons
* @version 1.0
*/
public class TimeInvocationHandler implements InvocationHandler {
// 因为我们调用方式的时候需要目标对象,所以我们通过处理器的构造方法传递目标对象
private Object target;
public TimeInvocationHandler(Object target) {
this.target = target;
}
/*
当代理对象调用代理方法时,注册在处理器中的invoke方法才会被JDK调用
invoke 的三个参数:
(1) Object proxy 代理对象
(2) Method method 目标对象的目标方法
(3) Object[] args 方法的参数列表
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
// 通过反射机制调用我们目标对象的方法
Object o = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - start)+"毫秒");
// 如果我们调用的方法有返回值,需要我们invoke方法传递一下返回值[继续返回]
return o;
}
}
编写我们的测试程序
package com.powernode.proxy.client;
import com.powernode.proxy.service.OrderService;
import com.powernode.proxy.service.OrderServiceImpl;
import com.powernode.proxy.util.ProxyUtil;
/**
* @author Bonbons
* @version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
/*
我们通过newProxyInstance在内存中创建字节码文件并生成代理类对象
(1)ClassLoader loader 代表目标对象的类加载器
(2)Class>[] interfaces 公共接口
(3)InvocationHandler h 处理调用器,包含增强代码的接口
*/
Object proxyObj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocationHandler(target));
// 代理对象调用方法
OrderService obj = (OrderService) proxyObj;
obj.generate();
obj.detail();
obj.modify();
// 调用有返回值的方法
String name = obj.getName();
System.out.println(name);
}
}

package com.powernode.proxy.util;
import com.powernode.proxy.service.TimeInvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author Bonbons
* @version 1.0
*/
public class ProxyUtil {
// 通过静态方法封装我们启用动态代理的那段代码,底层还是由JDK进行调用
public static Object newProxyInstance(Object target){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new TimeInvocationHandler(target));
}
}
此处客户端创建代理对象的代码可以替换为: Object proxyObj = ProxyUtil.newProxyInstance(target);
第一步,需要我们导入相关依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
第二步,准备一个目标类 UserService
package com.powernode.proxy.service;
/**
* @author Bonbons
* @version 1.0
*/
public class UserService {
// 登录验证方法
public boolean login(String username, String password){
if ("admin".equals(username) && "admin".equals(password)) {
return true;
}else{
return false;
}
}
// 退出登录
public void logout(){
System.out.println("系统正在退出...");
}
}
第三步,编写我们的方法拦截器 TimeMethodInterceptor,类似于JDK中的调用处理器
package com.powernode.proxy.service;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author Bonbons
* @version 1.0
*/
public class TimeMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long start = System.currentTimeMillis();
// 此处执行的是代理对象的方法
Object value = methodProxy.invokeSuper(o, objects);
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - start)+"毫秒");
return value;
}
}
第四步,编写我们的客户端程序 Client
package com.powernode.proxy.client;
import com.powernode.proxy.service.TimeMethodInterceptor;
import com.powernode.proxy.service.UserService;
import net.sf.cglib.proxy.Enhancer;
/**
* @author Bonbons
* @version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建字节码增强器[CGLIB 的核心对象,用来创建代理类]
Enhancer enhancer = new Enhancer();
// 目标对象是谁
enhancer.setSuperclass(UserService.class);
// 通过回调设置调用处理器 >> 方法拦截器接口 MethodInterceptor
enhancer.setCallback(new TimeMethodInterceptor());
// 创建代理对象
UserService userServiceProxy = (UserService) enhancer.create();
System.out.println("查看我们的代理对象: " + userServiceProxy);
// 调用代理方法 --登录 --退出
boolean login = userServiceProxy.login("admin", "admin");
System.out.println(login ? "登录成功" : "登录失败");
userServiceProxy.logout();
}
}

