一、Spring 整合 DbUtils
(一)Spring 整合 DbUtils
DbUtils是Apache的一款用于简化Dao代码的工具类,它底层封装了JDBC技术。 核心类:QueryRunner 用于执行增删改查的SQL语句 ResultSetHandler 这是一个接口,主要作用是将数据库返回的记录封装进实体对象
1、导入依赖的坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.15</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2、创建数据表
create table Person(
pid int primary key auto_increment,
pname varchar(100) not null unique,
Paddress varchar(100) not null
)
3、创建实体类
public class Person implements Serializable {
private Integer pid;
private String pname;
private String paddress;
}
4、编写持久层代码
public interface PersonDao {
//保存
void save(Person person );
//根据主键查询
Person findByPid(Integer pid);
//查询所有
List<Account> findAll();
}
public class PersonDao Impl implements PersonDao {
QueryRunner queryRunner=null;
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
@Override
public void save(Person person) {
try {
queryRunner.update("insert into person(pname,paddress)value(?,?)",
account.getPname(),
account.getAddress());
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
};
}
@Override
public Person findByAid(Integer pid) {
try {
//返回值封装到第二个参数位置
return queryRunner.query("select * from person where pid = ?",
new BeanHandler<>(Person.class), pid );
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public List<Person> findAll() {
try {
//返回值封装到第二个参数位置
return queryRunner.query("select * from person",
new BeanListHandler<>(Person.class) );
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
5、编写业务层代码
public interface PersonService {
//保存
void save(Person person);
//根据主键查询
Person findByAid(Integer pid);
//查询所有
List<Person> findAll();
}
public class AccountServiceImpl implements AccountService {
PersonDao personDao;
public void setPersonDao(PersonDao personDao) {
this.personDao= personDao;
}
@Override
public void save(Person person) {
personDao.save(person);
}
@Override
public Person findByAid(Integer pid) {
return personDao.findByAid(pid);
}
@Override
public List<Person> findAll() {
return personDao.findAll();
}
}
6、创建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.Springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.Springframework.org/schema/context" xsi:schemaLocation="http://www.Springframework.org/schema/beans https://www.Springframework.org/schema/beans/Spring-beans.xsd http://www.Springframework.org/schema/context https://www.Springframework.org/schema/context/Spring-context.xsd">
<!--datasource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.MySQL.jdbc.Driver"/>
<property name="url" value="jdbc:MySQL://localhost:3306/Spring02"/>
<property name="username" value="root"/>
<property name="password" value="adminadmin"/>
</bean>
<!--queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
</bean>
<!--PersonDaoImpl-->
<bean id="personDao" class="com.offcn.dao.impl.PersonDaoImpl">
<property name="queryRunner" ref="queryRunner"/>
</bean>
<!--PersonServiceImpl-->
<bean id="personService" class="com.offcn.service.impl.PersonServiceImpl">
<property name="personDao" ref="personDao"/>
</bean>
</beans>
7、测试代码
public class PersonServiceTest {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
PersonService personService =
(PersonService ) applicationContext.getBean("personService");
//测试保存
@Test
public void testSave() {
Person person = new Person();
person.setPname("张三");
person.setAddress("济南");
personService .save(person);
}
//测试查询
@Test
public void testFindByAid() {
Person person= personService.findByAid(2);
System.out.println(person);
}
//测试查询所有
@Test
public void testFindAll() {
List<Person> personList = personService.findAll();
for (Person person: personList ) {
System.out.println(person);
}
}
}
二、Spring整合各种数据源
Spring中配置数据源非常简单,只要两步:
1、引入数据源坐标
2、在配置文件中配置DataSource的bean
(一)整合C3P0数据源
1、添加依赖坐标:
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
2、在Spring配置文件中添加配置内容
<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="数据库驱动名称"/>
<property name="jdbcUrl" value="数据库url地址"/>
<property name="user" value="数据库名称"/>
<property name="password" value="数据库密码"/>
</bean>
(二)Spring-jdbc 自带数据源
Spring自带的数据源,没用到池技术,适合开发、测试环境使用
1、添加依赖坐标
<dependency>
<groupId>org.Springframework</groupId>
<artifactId>Spring-jdbc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
2、在配置文件中添加数据源的配置
<bean
id="dataSource" class="org.Springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClass" value="数据库驱动名称"/>
<property name="jdbcUrl" value="数据库url地址"/>
<property name="user" value="数据库名称"/>
<property name="password" value="数据库密码"/>
</bean>
(三)阿里巴巴数据源 Druid
1、添加依赖坐标
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.15</version>
</dependency>
2、在配置文件中添加数据源的配置
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClass" value="数据库驱动名称"/>
<property name="jdbcUrl" value="数据库url地址"/>
<property name="user" value="数据库名称"/>
<property name="password" value="数据库密码"/>
</bean>
(四)DBCP数据源
1、添加依赖坐标
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.6.0</version>
</dependency>
2、在配置文件中添加数据源的配置信息
<bean id="basicDataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClass" value="数据库驱动名称"/>
<property name="jdbcUrl" value="数据库url地址"/>
<property name="user" value="数据库名称"/>
<property name="password" value="数据库密码"/>
</bean>
三、基于注解的IOC配置
(一)常用注解介绍
1、对象实例化注解
@Component
用于实例化对象,相当于配置文件中的< bean id="" class=""/> 它支持一个属性value,相当于xml中bean的id。如果不写,默认值为类名的首字母小写
@Controller
@Service
@Repository
这三个注解的功能跟@Component完全一样,只不过他们三个比较有语义化。
@Controller 一般标注在变现层的类上
@Service 一般标注在业务层的类上
@Repository 一般标注在持久层的类上
推荐使用这三个,当一个类实在不好归属在这三个层上时,再使用@Component!!!
2、依赖注入的注解
@Autowired 按照类型注入。
相当于配置文件中的< property name="" ref="">
当使用注解注入属性时,set 方法可以省略。
当要注入的属性存在多个实现类时会报错。
@Qualifier
在@Autowired注入的基础之上,再按照 Bean 的 id 注入。
一般在@Autowired注入存在多个实现类的时候,使用@Qualifier按照bean的id选取
@Resource 直接按照 Bean 的 id 注入
@Value
用于简单数据类型的注入,相当于< property name="" value="" >
3、用于改变注入范围的注解
@Scope
用于指定bean的作用范围,相当于配置文件中的< bean scope="">
(二)基于注解的IOC配置
1、创建工程引入坐标
<dependencies>
<dependency>
<groupId>org.Springframework</groupId>
<artifactId>Spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
</dependencies>
2、使用@Component注解配置管理的资源
//使用@Component向Spring声明一个bean,
//相当于xml中<bean id="book" class="com.itheima.anno.Book" />
@Component("book")
public class Book {
private int bid;
private String bname;
private double bprice;
}
3、创建配置文件,并开启注解的支持
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.Springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.Springframework.org/schema/context" xsi:schemaLocation="http://www.Springframework.org/schema/beans https://www.Springframework.org/schema/beans/Spring-beans.xsd http://www.Springframework.org/schema/context " >https://www.Springframework.org/schema/context/Spring-context.xsd">
<!-- 告诉 Spring 创建容器时要扫描的包,可以理解为寻找注解的过程 --> <context:component-scan base-package="com.offcn"/>
</beans>
4、测试
public class TestSpringAnno {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
@Test public void test() {
Book book = (Book) applicationContext.getBean("book");
book.desc();
}
}
(三) 注解案例
1、基于注解的CRUD
拷贝原来的案例工程进行修改
(1)使用注解修改持久层内容
@Repository("personDao")
public class PersonDaoImpl implements PersonDao {
@Autowired
private QueryRunner queryRunner;
//其余代码跟原来一模一样
}
(2)使用注解修改业务层
@Service("personService")
//使用注解完成bean的声明
//相当于xml中的
<bean id="personService" class="com.offcn.dao.impl.PersonServiceImpl">
public class PersonServiceImpl implements PersonService {
//使用注解完成依赖注入
@Autowired
private PersonDao personDao;
//其余代码跟原来一模一样
}
(3)调整配置文件,去掉关于service和dao的bean
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.Springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.Springframework.org/schema/context" xsi:schemaLocation="http://www.Springframework.org/schema/beans https://www.Springframework.org/schema/beans/Spring-beans.xsd http://www.Springframework.org/schema/context " >https://www.Springframework.org/schema/context/Spring-context.xsd">
<context:component-scan base-package="com.offcn" />
<!--datasource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.MySQL.jdbc.Driver"/>
<property name="url" value="jdbc:MySQL://localhost:3306/Spring02"/>
<property name="username" value="root"/>
<property name="password" value="adminadmin"/>
</bean>
<!--queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
</bean>
</beans>
(4)测试代码
使用原来的代码即可
四、Spring和junit的整合
这是Spring提供的对Junit单元测试的一种支持。使用步骤如下:
1:引入坐标
<dependency>
<groupId>org.Springframework</groupId>
<artifactId>Spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
2:在测试类上使用@RunWith指定Spring的单元测试运行器
@RunWith(SpringJUnit4ClassRunner.class)
public class PersonServiceTest {
}
3:使用@ContextConfiguration指定配置文件,它支持文件和类的形式
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")
public class PersontServiceTest {
}
4:下面可以直接使用@Autowired的形式,让Spring容器为我们注入需要的bean了
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")
public class PersonServiceTest {
@Autowired
PersonService personService
}
五、动态代理模式详解
(一)动态代理模式简介
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性
代理类在程序运行时创建的代理方式被成为动态代理。动态代理是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
(二) 动态代理模式入门案例
需求: A账户需要转账给B账户
1、 Version 1.0 使用传统的分层调用方式,同时在service中添加事务管理
(1)构建我们的Dao层代码
public class AccountDaoImpl implements AccountDao {
//创建QueryRunner的时候不再传入数据源
private QueryRunner queryRunner = new QueryRunner();
private Connection connection;
//在构造方法中传入Connection,这样就可以保证SQL语句使用的是同一个connection
public AccountDaoImpl(Connection connection) {
this.connection = connection;
}
@Override
public Account findByName(String name) {
//在执行的时候传入connection,connection相同,事务就相同
return queryRunner.query(connection,"select * from account where name = ?",
new BeanHandler<Account>(Account.class), name);
}
@Override
public void updateByName(Account account) {
queryRunner.update(connection,"update account set balance = ? where name = ?",
account.getBalance(), account.getName());
}
}
(2)构建Service服务层代码
public class AccountServiceImpl implements AccountService {
Connection connection = DataSourceUtil.getConnection();
private AccountDao accountDao = new AccountDaoImpl(connection);
@Override
public void transfer(String sourceAccountName, String targetAccountName, Float amount) {
try {
connection.setAutoCommit(false);
//查询余额
Account sourceAccount = accountDao.findByAccountName(sourceAccountName);
Account targetAccount = accountDao.findByAccountName(targetAccountName);
//增减
sourceAccount.setBalance(sourceAccount.getBalance() - amount);
targetAccount.setBalance(targetAccount.getBalance() + amount);
//更新余额
accountDao.update(sourceAccount);
//int i = 1 / 0;
accountDao.update(targetAccount);
connection.commit();
} catch (Exception e) {
System.out.println("业务出现异常");
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
代码问题:Connection是数据库连接,属于持久层的东西,但现在却跑到了业务层里,又出现了层次混乱。
2、 V2.0 从业务层剥离跟持久层相关的代码
使用ThreadLocal从业务层剥离跟持久层相关的代码
(1)创建一个事务管理器的工具类,使用ThreadLocal存放Connection
public class TxManager {
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
//获取连接
public static Connection getConnection() {
Connection connection = tl.get();
if (connection == null) {
connection = DataSourceUtil.getConnection();
tl.set(connection);
}
return connection;
}
//开启事务
public static void begin() {
try {
getConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public static void commit() {
try {
getConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//回滚事务
public static void rollback() {
try {
getConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭事务
public static void close() {
try {
getConnection().close();
tl.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(2)构建service服务层代码:
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String sourceAccountName, String targetAccountName, Float amount) {
try {
TxManager.begin();
//查询余额
Account sourceAccount = accountDao.findByAccountName(sourceAccountName);
Account targetAccount = accountDao.findByAccountName(targetAccountName);
//增减
sourceAccount.setBalance(sourceAccount.getBalance() - amount);
targetAccount.setBalance(targetAccount.getBalance() + amount);
//更新余额
accountDao.update(sourceAccount);
int i = 1 / 0;
accountDao.update(targetAccount);
TxManager.commit();
} catch (Exception e) {
System.out.println("业务出现异常");
TxManager.rollback();
} finally {
TxManager.close();
}
}
}
(3)构建数据操作层Dao代码
public class AccountDaoImpl implements AccountDao {
private QueryRunner queryRunner = new QueryRunner();
@Override
public Account findByAccountName(String accountName) {
try {
return queryRunner.query(TxManager.getConnection(),
"select * from account where name = ?",
new BeanHandler<>(Account.class), accountName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void update(Account account) {
try {
queryRunner.update(TxManager.getConnection(), "update account set balance = ? where name = ?",
account.getBalance(), account.getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
代码问题:在业务层的代码中存在一些跟业务无关 、跟事务相关的公共代码。
3、 V3.0 使用动态代理优化代码
(1)GDK动态代理是基于接口实现的
a.创建动态代理类
//动态代理类
public class AccountServiceProxyImpl implements InvocationHandler {
private AccountService target;
//传进来的就是被代理类
public AccountServiceProxyImpl(AccountService target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
TxManager.begin();
//调被代理类实现核心业务
//target.transfer(sourceAccountName, targetAccountName, amount);
method.invoke(target,args);
TxManager.commit();
} catch (Exception e) {
System.out.println("业务出现异常");
TxManager.rollback();
} finally {
TxManager.close();
}
return null;
}
}
b.测试
public class AccountTest {
private AccountService accountService = new AccountServiceImpl();
@Test
public void testTransfer() {
//ClassLoader loader, 类加载器,因为代理类是运行时才真正产生的,所以需要类
加载器的加载。跟被代理类使 用同一个
//Class<?>[] interfaces, 接口,就是用被代理类实现的接口,目的是使得代理类和被
代理类拥有相同的方法
//InvocationHandler h 代理策略,里面是我们的道理逻辑
AccountService instance = (AccountService) Proxy.newProxyInstance(
accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new AccountServiceProxyImpl(accountService)
);
instance.transfer("A01","A02",2F);
}
}
代码问题:GDK动态代理要求被代理类一定要有接口,当没有接口时便无法使用了。
(2)CGLIB动态代理
CGLIB动态代理是基于子类实现的
a.添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
b.构建动态代理工具类
public class AccountServiceProxyImpl implements InvocationHandler {
private AccountServiceImpl target;
//传进来的就是被代理类
public AccountServiceProxyImpl(AccountServiceImpl target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
TxManager.begin();
//调被代理类实现核心业务
//target.transfer(sourceAccountName, targetAccountName, amount);
method.invoke(target,args);
TxManager.commit();
} catch (Exception e) {
System.out.println("业务出现异常");
TxManager.rollback();
} finally {
TxManager.close();
}
return null;
}
}
c.测试
public class AccountTest {
private AccountServiceImpl accountService = new AccountServiceImpl();
@Test
public void testTransfer() {
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(AccountServiceImpl.class);
//设置增强
enhancer.setCallback(new AccountServiceProxyImpl(accountService));
//获取代理后的实例
AccountServiceImpl instance = (AccountServiceImpl) enhancer.create();
instance.transfer("A01", "A02", 2F);
}
}
通过上面的案例,我们体会到一种新的编程思想:
当核心业务(转账)和增强业务(事务)同时出现时,我们可以在开发时对他们分别开发,运行时再组装在一起(使用动态代理的方式)。
这样做的好处是:
1. 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
2. 代码复用性高:增强代码不用重复书写
这就是一种AOP的思想。