第二天:Spring整合各种数据源+基于注解的IOC配置+动态代理

一、Spring 整合 DbUtils

(一)Spring 整合 DbUtils

DbUtilsApache的一款用于简化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、在配置文件中配置DataSourcebean

(一)整合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,相当于xmlbeanid。如果不写,默认值为类名的首字母小写

@Controller

@Service

@Repository

这三个注解的功能跟@Component完全一样,只不过他们三个比较有语义化。

@Controller 一般标注在变现层的类上

@Service 一般标注在业务层的类上

@Repository 一般标注在持久层的类上

推荐使用这三个,当一个类实在不好归属在这三个层上时,再使用@Component!!!

 

2、依赖注入的注解

@Autowired 按照类型注入。

相当于配置文件中的< property name="" ref="">

当使用注解注入属性时,set 方法可以省略。

当要注入的属性存在多个实现类时会报错。

@Qualifier

@Autowired注入的基础之上,再按照 Bean id 注入。

一般在@Autowired注入存在多个实现类的时候,使用@Qualifier按照beanid选取

@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注解配置管理的资源

//使用@ComponentSpring声明一个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)调整配置文件,去掉关于servicedaobean

<?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账户

 

1Version 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) {

//在执行的时候传入connectionconnection相同,事务就相同

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是数据库连接,属于持久层的东西,但现在却跑到了业务层里,又出现了层次混乱。

 

2V2.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);

}

}

}

代码问题:在业务层的代码中存在一些跟业务无关 、跟事务相关的公共代码。

 

3V3.0 使用动态代理优化代码

1GDK动态代理是基于接口实现的

 

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的思想。

 

 

原文地址:https://www.cnblogs.com/masterhxh/p/13840903.html