一、Spring概述
1.1 Spring概述
1.1.1 什么是spring?
spring是分层的javaSE/EE应用 全栈 轻量级开源框架,以IOC为核心和Aop为内核,提供了展现层MVC和持久层SpringJDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多的第三方框架和类库。
1.1.2 代码展示如何解耦?
常规调用:业务层层调用持久层(dao层=new dao层实现类)
表现层调用业务层(service层= new service层实现类)
dao层代码:
public interface UserDao {
void saveUser();
}
dao层实现类:
/**
- 用户的持久层实现类
*/
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存账户");
}
}
service层代码:
/**
- 业务层接口
- 管理用户
*/
public interface UserService {
/**
* 模拟保存
*/
void saveUser();
}
service层实现类:
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
//private UserDao userDao = (UserDao) BeanFactory.getBean("userDao");
/**
*业务层调用持久层
*/
@Override
public void saveUser() {
userDao.saveUser();
}
}
主方法类:
/**
* 表现层调用业务层
*/
public class Client {
public static void main(String[] args) {
for (int i = 1; i < 3; i++) {
UserService userService = new UserServiceImpl();
//UserService userService = (UserService) BeanFactory.getBean("userService");
System.out.println(userService);
userService.saveUser();
}
}
}
通过创建外部properties配置文件,存入key以及对应的实现类的完全限定名。通过工厂模式,将各层之间的调用方式改变。通过bean工厂生产对象,从而实现解耦。中间利用了反射来创建工厂对象,将生产出来的对象放在容器中,从而避免反复创建对象带来的资源消耗。调用对象的模式也从多例模式转变为单例模式。
实例代码如下:
首先是BeanFactory工厂类:
/**
* 一个创建bean对象的工厂
*
* Bean在计算机中,有可重用组件的含义
* JavaBean != 实体类 用java语言编写的可重用组件
*
* 特就是创建我们的service和Dao对象
*
* 第一个:需要一个配置文件,来配置service和dao
* 配置的内容,唯一标识 = 全限定类名(key = value)
* 第二个:通过读取配置文件的配置的内容,反射创建对象
*/
public class BeanFactory {
//创建一个静态的properties对象
private static Properties p;
//定义一个Map用与存放我们要创建对象,称之为容器
private static Map<String ,Object> beans;
//创建一个静态代码块用来获取bean.Properties配置文件
static {
try {
//new 一个Properties对象
p = new Properties();
//利用类加载器获取bean.properties的流对象
InputStream ip = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
//使用properties对象的load方法载入流对象
p.load(ip);
//实例化容器
beans = new HashMap<String,Object>();
//利用properties的keys方法获取所有的key
Enumeration keys = p.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = p.getProperty(key);
//反射创建实例对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入beans容器中
beans.put(key,value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据bean的key获取对象value
*
* @param beanName
* @return
*/
public static Object getBean(String beanName) {
return beans.get(beanName);
}
/**
* newInstance
* public T newInstance()
* throws InstantiationException,
* IllegalAccessException创建此 Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的 new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。
* 注意,此方法传播 null 构造方法所抛出的任何异常,包括已检查的异常。使用此方法可以有效地绕过编译时的异常检查,而在其他情况下编译器都会执行该检查。 Constructor.newInstance 方法将该构造方法所抛出的任何异常包装在一个(已检查的)InvocationTargetException 中,从而避免了这一问题。
*
*
* 返回:
* 此对象所表示的类的一个新分配的实例。
* 抛出:
* IllegalAccessException - 如果该类或其 null 构造方法是不可访问的。
* InstantiationException - 如果此 Class 表示一个抽象类、接口、数组类、基本类型或 void; 或者该类没有 null 构造方法; 或者由于其他某种原因导致实例化失败。
* ExceptionInInitializerError - 如果该方法引发的初始化失败。
* SecurityException - 如果存在安全管理器 s,并满足下列任一条件:
* 调用 s.checkMemberAccess(this, Member.PUBLIC) 拒绝创建该类的新实例
* 调用者的类加载器不同于也不是当前类的类加载器的一个祖先,并且对 s.checkPackageAccess() 的调用拒绝访问该类的包
* @param beanName
* @return
*/
/*//定义一个方法,从流对象中获取配置文件对应完全限定类的路径,思路,利用反射的思想创建bean
//利用配置文件,将对象变为bean存储在工厂中
public static Object getBean(String beanName){
//创建bean对象
Object bean = null;
//调用properties对象的getProperty方法传入参数beanName获取响应的key,也就是bean.properties配置文件的key
String beanPath = p.getProperty(beanName);
try {
//通过反射,创建该路径下的类对象实例
bean = Class.forName(beanPath).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return bean;
}*/
}
然后是更改之后的业务层,跟表现层主方法类:
public class UserServiceImpl implements UserService {
//private UserDao userDao = new UserDaoImpl();
private UserDao userDao = (UserDao) BeanFactory.getBean("userDao");
/**
*业务层调用持久层
*/
@Override
public void saveUser() {
userDao.saveUser();
}
}
/**
* 表现层调用业务层
*/
public class Client {
public static void main(String[] args) {
for (int i = 1; i < 3; i++) {
//UserService userService = new UserServiceImpl();
UserService userService = (UserService) BeanFactory.getBean("userService");
System.out.println(userService);
userService.saveUser();
}
}
}
1.1.3 IOC
控制反转,将原本由程序员控制的创建对象的权利交由工厂处理,削减了计算机的耦合,大大降低了程序各个对象之间的依赖。
二、使用spring的IOC解决程序的耦合问题
1.1 使用spring的ioc来解决程序间的依赖关系
使用DI实现依赖注入,bean通过bean注入的步骤是:
- 创建spring的xml配置文件,将对象通过配置文件的方式交由spring容器进行管理。
- 通过bean标签中的属性对注入的bean对象进行管理,示例如下:
<bean id="userService" scope="prototype" init-method="init" destroy-method="destroy" class="com.itheima.service.impl.UserServiceImpl"></bean>
其中id标签代表自定义的bean组件的名称,scope便签可以选择单例模式或者多例模式即singleton和prototype。init-method表示bean组件通过spring创建之后执行的方法,destroy-method表示容器销毁之后执行的方法。
自我理解bean的执行周期,在单例模式下:1)容器创建时立马创建对象 ,2)初始化对象,3)执行对象中的方法或者不执行, 4)销毁容器,对象死亡
在多例模式下:1)使用对象的时候spring帮我们创建对象 ,2)对象只要在使用过程中就会一直存活,3)多例模式不会自动销毁容器,当对象长时间不使用,垃圾回收期会回收对象或者手动关闭容器,对象此时就是死亡的状态。
- bean的作用域
作用域的作用,用于指定bean对象的作用范围:
singleton:单例模式
prototype:多例模式
request:作用域web应用的请求域
session:作用域web应用的会话范围
globalSession:作用于集群中多个服务器的session范围 - bean对象创建的三种方式:
- 构造函数创建
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
- 构造函数创建
//使用构造函数创建
public UserServiceImpl() {
}
XML配置:
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>
- 使用某个类的方法创建
//使用InstanceFactory类的方法创建bean对象
public class InstanceFactory {
private UserService getUserService(){
return new UserServiceImpl();
}
}
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="userService" factory-bean="instanceFactory" factory-method="getUserService"></bean>
- 使用静态类的静态方法创建对象
//使用静态方法创建bean
public class StaticFactory {
private static UserService getUserService(){
return new UserServiceImpl();
}
}
<bean id="userService" class="com.itheima.factory.StaticFactory" factory-method="getUserService"></bean>
- spring的依赖注入DI
作用:降低耦合,也就是对象之间的互相调用
能够注入的三种数据类型:String的基本类型、bean类型、集合
注入的方式:
1. 构造函数注入–不推荐使用
标签出现的位置:bean标签的内部
标签的属性:
type:用于指定要注入的数据的数据类型,改数据类型也是构造函数中某个或者某些参数的类型
index:用于指定注入的数据给构造函数中指定索引位置的参数赋值,索引的位置从0开始
name:用于指定给构造函数中指定名称的参数赋值
以上三个用于指定给构造函数的那个参数赋值==
value:用于提供基本类型String类型的数据
ref:用于指定其他的bean类型数据,要求在spring的ioc容器中出现的bean对象
优势:
在获取bean对象时,注入数据是必须的操作
弊端:
改变了bean对象的实例化方式,使我们想使用这些对象时,如果不用这些数据也必须提供
示例代码如下:
//构造函数注入
public UserServiceImpl(String name, Integer age, Date time){
this.name = name;
this.age = age;
this.time = time;
}
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<constructor-arg index="0" value="张三"/>
<constructor-arg name="age" value="81"/>
<constructor-arg name="time" ref="now"/>
</bean>
<bean id="now" class="java.util.Date"></bean>
第二种setter方法注入:
public class UserServiceImpl2 implements UserService {
private String name;
private Integer age;
private Date time;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setTime(Date time) {
this.time = time;
}
<!--set方法注入=====比较常用
标签:property
位置:bean标签内部
标签的属性:
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String类型的数据
ref:指定其他的bean类型数据
优势:
创建对象时,没有明确的限制,可以直接使用默认构造函数
弊端:
如果某个成员必须有值,获取对象时有可能set方法没有执行
-->
<bean id="userService1" class="com.itheima.service.impl.UserServiceImpl2">
<property name="name" value="22"></property>
<!--像这样,容器无法发现set有没有执行
<property name="age" value="20"></property>-->
<property name="time" ref="now"></property>
</bean>
特殊种类集合类型的注入:
<!--集合类型的注入
用于给List结构集合注入的标签有: list set array
用于给Map结构集合注入的标签有: map props
结构一样标签可以互换
-->
<bean id="userService3" class="com.itheima.service.impl.UserServiceImpl3">
<property name="myt">
<array>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
</array>
</property>
<property name="lists">
<list>
<value>a</value>
<value>b</value>
<value>c</value>
</list>
</property>
<property name="maps">
<map>
<entry key="s" value="ss"></entry>
<entry key="b" value="bb"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="ssss">bbbb</prop>
</props>
</property>
1.2 常用的IOC注解,按照作用分类。
曾经的XML的配置:
<bean id="accountService" scope="singleton" init-method="findAll" destroy-method="findAll" class="com.itheima.service.impl.AccountServiceImpl">
<!--注入dao对象-->
<property name="accountDao" ref="accountDao"></property>
</bean>
- 用于创建对象的:作用和XML配置文件中编写一个标签实现的功能是一样的。@Component注解,用于将当前类存入spring容器中,属性key,value:用于在核定bean的id,默认bean的id为类名称小写。要想加了注解的类可以被扫描为bean,有两种配置方式。使用配置文件配置bean时,1)在spring的bean.xml文件中加入context头文件和对应的标签指定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"
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">
<!--用于告知spring创建容器时需要扫描的包,配置所需的标签不是在beans的约束中,而是一个
名称为context名称空间和约束中-->
<context:component-scan base-package="com.itheima"></context:component-scan>
</beans>
2)使用注解配置时,需要在配置类上加上@CompoentScan注解,value为需要扫描的包的类路径。
3)@Component注解的衍生注解,有@Controller:一般用于表现层;@Service 一般用于业务层 ;@Repository一般用于持久层,以上三个注解和component的作用是一样的,使三层对象更清晰
@ComponentScan("com.itheima")//这个注解加载配置类类名上方
- 用于注入数据的:作用和XML配置文件中的bean标签里面的标签的作用是一样的。
注解:@Autowired作用:自动按照类型注入,只要容器中有唯一的bean对象类型和要注入的变量类型匹配就可有注入成功。出现位置,可以是成员变量上也可以是方法上。使用set注入不需要set方法。可以搭配@Qualifier一起使用,在按照类型注入的基础上再按照名称进行进一步筛选。还有一个注解@Resource可以直接按照bean的名称注入,不用依靠@Autowired,他的属性是name。@Value注解:用于注入基本类型和String类型的数据,用于指定数据的值,可以使用SPEL表达式
@Autowired()//按照类型注入
@Qualifier("accountDao")//用于指定bean的名称注入,搭配@注解Autowired,按照名称注入
@Resource(description = "accountDao")//直接按照bean的名称注入,可以单独使用
@Value("${jdbc.driver}")//SPEL表达式
private String driver;
@Value("2")//基本类型数据注入
private int x;
- 用于改变作用范围的:作用和bean标签中的标签一样
@Scope改变bean的作用范围,默认是Singleton - 和生命周期有关的:init-method destroy-method作用一样
@PreDestory 用于指定销毁方法@PostConstruct用于指定初始化方法
1.3 IOC案例
- 准备数据表
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`money` float NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES (1, '嘿嘿嘿', 10000);
INSERT INTO `account` VALUES (3, 'ccc', 1000);
INSERT INTO `account` VALUES (4, '仨', 1000);
- pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>anli</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
</project>
- 持久层,业务层,配置文件代码(没有注释,请谅解)
实体类,省去get ,set ,toString方法
public class Account {
private Integer id;
private String name;
private Float money;
}
持久层以及持久层实现类:
public interface AccountDao {
List<Account> findAll();
Account findById(Integer id);
void save(Account account);
void deleteById(Integer id);
void updateAccount(Account account);
}
持久层实现类,使用dbUtil操作数据库
public class AccountDaoImpl implements AccountDao {
//创建QueryRunner对象
private QueryRunner runner;
//创建set方法没使用set方法注入bean
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
@Override
public List<Account> findAll() {
try {
return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findById(Integer id) {
try {
return runner.query("select *from account where id = ?",new BeanHandler<Account>(Account.class),id);
} catch (Exception e) {
throw new RuntimeException();
}
}
@Override
public void save(Account account) {
try {
runner.update("insert into account(name,money)values(?,?) ",account.getName(),account.getMoney());
} catch (Exception e) {
throw new RuntimeException();
}
}
@Override
public void deleteById(Integer id) {
try {
runner.update("delete from account where id = ?",id);
} catch (Exception e) {
throw new RuntimeException();
}
}
@Override
public void updateAccount(Account account) {
try {
runner.update("update account set name = ?, money = ? where id =?",account.getName(),account.getMoney(),account.getId());
} catch (Exception e) {
throw new RuntimeException();
}
}
}
业务层以及业务员层实现类:
public interface AccountService {
List<Account> findAll();
Account findById(Integer id);
void save(Account account);
void deleteById(Integer id);
void updateAccount(Account account);
}
public class AccountServiceImpl implements AccountService {
//使用set方法注入bean
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAll() {
return accountDao.findAll();
}
@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}
@Override
public void save(Account account) {
accountDao.save(account);
}
@Override
public void deleteById(Integer id) {
accountDao.deleteById(id);
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
}
最最重要的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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!--注入dao对象-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="runner" ref="runner"></property>
</bean>
<!--每次使用对象都创建新的对象,所以选择bean作用范围为多例-->
<bean id="runner" scope="prototype" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db3?characterEncoding=UTF-8"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
抽取出来的公共模块,用来加载配置文件,获取bean:
public class BeanUtil {
public static AccountService getBean(){
ApplicationContext as = new ClassPathXmlApplicationContext("bean.xml");
AccountService accountService = as.getBean("accountService", AccountService.class);
return accountService;
}
}
案例的另外一种写法,使用注解,抛弃配置文件。
数据表,pom文件同上:
数据库配置文件:jdbcConfig.xml
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db3?characterEncoding=UTF-8
jdbc.user=root
jdbc.password=root
持久层实现类的一些改变:
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
//通过bean的类型注入
@Autowired
private QueryRunner runner;
业务层实现类的改变:注解注入,不用set方法
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired()//按照类型注入
//@Qualifier("accountDao")//用于指定bean的名称注入,搭配@注解Autowired,按照名称注入
//@Resource(description = "accountDao")//直接按照bean的名称注入,可以单独使用
private AccountDao accountDao;
springConfig配置类
@Configuration//声明这是一个配置类
public class SpringConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.user}")
private String username;
@Value("${jdbc.password}")
private String password;
//通过调用构造函数实例化,将返回的对象作为bean加入容器,并命名为runner
@Bean(name="runner")
@Scope("prototype")
//@Qualifier可以直接指定响应的参数
public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource c = new ComboPooledDataSource();
c.setDriverClass(driver);
c.setJdbcUrl(url);
c.setUser(username);
c.setPassword(password);
return c;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
使用注解的加载容器的方式:
public class BeanUtil {
public static AccountService getBean(){
ApplicationContext as = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AccountService accountService = as.getBean("accountService", AccountService.class);
return accountService;
}
}
主配置类:
@ComponentScan("com.itheima")//表示创建容器时需要扫描的包
@Import(SpringConfig.class)//将子配置类导入总配置类
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
@ComponentScan用于指定创建容器时需要扫描的包
@Import 用于导入其他的配置类,有Import注解的父配置类,导入的式子配置类。可以同时导入多个子配置类。
@PropertyResource 用于指定properties文件的名称和位置,关键字:classpath表示类路径下。
@Qualifier//通过参数名称直接指定参数
public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource dataSource) {}
使用spring-test spring整合junit单元测试类完成测试,去除冗余的重复代码,并且可以解耦
//表示替换默认测试main方法
@RunWith(SpringJUnit4ClassRunner.class)
/**表示使用哪个配置文件或者配置类作为启动类
* locations:指定xml文件的位置,加上classpath关键字表示在类路径下
classes:指定注解类所在位置
*/
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountTest {
@Autowired
private AccountService as;
@Test
public void findAll(){
//获取全部用户信息
List<Account> all = as.findAll();
for (Account account : all) {
System.out.println(account);
}
}
@Test
public void findOne(){
Account byId = as.findById(1);
System.out.println(byId);
}
@Test
public void save(){
Account account = new Account();
account.setName("仨");
account.setMoney(1000f);
as.save(account);
}
@Test
public void delete(){
as.deleteById(2);
}
@Test
public void update(){
Account account = new Account();
account.setId(1);
account.setName("嘿嘿嘿");
account.setMoney(10000f);
as.updateAccount(account);
}
}
三、AOP
1.1 动态代理
1.代理模式的概念
代理模式是23中设计模式中的一种,是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B旺旺实现了一个看接口,A也会去实现接口。但是B是真正的实现类,那么A借用了B的方式去实现接口的方法。A可以通过增强B,在调用B的方法前后做些其他的事情。如果例子中代理A写死了B,那么A就是B的静态代理对象,如果A代理的对象时不确定的,那么就是动态代理,B是被代理对象,B是一个接口的实现类。
动态代理中常见的实现是,JDK动态代理和cglib动态代理。
2. 使用jdk的方式实现动态代理
接口代码:
public interface Proxy1 {
/**
* 销售产品
* @param money
*/
void sale(int money);
}
实现类:
/**
* 生产者
*/
public class Produce {
/**
* 销售产品
* @param money
*/
public void sale(int money) {
System.out.println("销售产品"+money);
}
}
/**
* 模拟消费者
*/
public class Client {
public static void main(String[] args) {
final Produce produce = new Produce();//注此处要用final修饰
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 设计的类proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理对象至少使用一个接口
* newProxyInstance接口的方法参数:
* ClassLoader:类加载器
* 用于加载代理对象字节码的,和被代理对象使用相同的类加载器。固定写法
* Class[]:字节码数组
* 用于让代理对象和被代理对象具有相同方法
* InvocationHandler:用于提供增强的代码
* 让我们写如何代理,一般是一些该接口的实现类,通常情况下都是匿名实现类,但不是必须的
*
*
*/
//创建代理对象代理对象的类型为代理接口
Proxy1 proxy1 = (Proxy1) Proxy.newProxyInstance(
produce.getClass().getClassLoader(),
produce.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会进过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnVale = null;
//获取方法执行的参数
int money = (int)args[0];
//判断当前方法是不是销售
if ("sale".equals(method.getName())){
returnVale = method.invoke(produce,(int)(money*0.9));
}
return returnVale;
}
});
proxy1.sale(10000);
}
}
基于cglib的动态代理:
加入cglib依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
/**
* 生产者
*/
public class Produce{
/**
* 销售产品
* @param money
*/
public void sale(int money) {
System.out.println("销售产品"+money);
}
/**
* 售后
*/
public void afterSale(int money){
System.out.println("售后:" + money);
}
}
使用cglib完成动态代理:
/**
* 模拟消费者
*/
public class Client {
public static void main(String[] args) {
final Produce produce = new Produce();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于子类的动态代理:
* 涉及的类enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhance类中的create方法
* 创建代理对象的要求:
* 被代理对象不能是最终类
* create的方法参数:
* Class字节码:
* 用于指定被代理对象的字节码
*
* callBack:用于提供增强的代码
* 让我们写如何代理,一般是一些该接口的实现类,通常情况下都是匿名实现类,但不是必须的
* 一般使用该接口的子接口实现类:MethodInterceptor
*
*
*/
Produce p = (Produce) Enhancer.create(produce.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理对象的参数是一样的
* @param methodProxy:当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//创建增强的代码
Object returnValue = null;
//获取方法执行的参数
int money = (int)args[0];
//判断当前方法是不是销售
if ("sale".equals(method.getName())){
returnValue = method.invoke(produce,(int)(money*0.8));
}
return returnValue;
}
});
p.sale(1000);
}
}
1.2 spring基于XML和注解的AOP配置
实现代码:
接口
/**
* 账户的业务接口
*/
public interface AccountService {
/**
* 模拟保存账户
*/
void saveAccount();
/**
* 模拟更新账户
*/
void updateAccount(int i);
/**
* 模拟删除账户
*/
int deleteAccount();
}
实现类
public class AccountServiceImpl implements AccountService {
@Override
public void saveAccount() {
System.out.println("执行保存");
}
@Override
public void updateAccount(int i) {
System.out.println("执行更新");
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
日志配置
/**
* 用于记录日志的工具类,里面提供了公共的代码
*/
public class Logger {
/**
* 用于打印日志,计划让其在切入点方法之前执行(切入点是业务层方法)
*/
public void printLog(){
System.out.println("Logger开始记录日志");
}
}
XML配置
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc,把service对象配置进去-->
<bean id="accountService" class="com.itheima.serivice.impl.AccountServiceImpl"></bean>
<!--spring中基于XML的AOP的配置步骤
1.把通知bean交给spring管理
2.使用aopConfig标签表名开始AOP的配置
3.使用aop:aspect标签表名配置的切面:
id属性:为切面提供唯一标识
ref属性:指定通知类bean的id
4.在aop:aspect标签的内部使用对应的标签来配置通知的类型
我们现在的示例是在切入点方法执行之前调用日志类的printLog方法,所以是前置通知aop:before
method属性:用于指定Logger类中那哪个方法是前置通知
pointcut属性用于指定对业务中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名,包名。。。类名.方法名.参数别表
标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符*,表示任意值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名使用通配符,表示任意包,有几级包就是用几个*
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用..表示当前包或者子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现统配
* *..*.*()
使用类型的通配符
* *..*.*(int)
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常方法:
一般是切入到业务层实现类下的所有方法:
* com.itheima.serivice.impl.*.*(..)
-->
<!--配置logger类-->
<bean id="logger" class="com.itheima.serivice.util.Logger"></bean>
<!--配置aop-->
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="printLog" pointcut="execution(* com.itheima.serivice.impl.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
测试方法
/**
* 测试aop配置
*/
public class AopTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
accountService.saveAccount();
accountService.updateAccount(1);
accountService.deleteAccount();
}
}
返回结果
Logger开始记录日志
执行保存
Logger开始记录日志
执行更新
Logger开始记录日志
执行了删除
1.3 AOP的使用
1. 基于XML配置文件文件的使用:
实体类
/**
* 账户的实体类
*/
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
}
持久层实现类:
/**
* 账户的持久层实现类
*/
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
//set方式注入
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
//set方式注入
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public List<Account> findAllAccount() {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountById(Integer accountId) {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void saveAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void deleteAccount(Integer accountId) {
try{
runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
util工具类:
连接数据库工具:
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
//获取当前线程上的连接
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
//set方式注入DataSource数据源
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
事务管理工具类:
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
//set方式注入连接模板对象
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
System.out.println("start");
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
System.out.println("提交事务");
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
System.out.println("回滚事务");
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
System.out.println("释放连接");
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
xml配置文件:
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置Service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao对象-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db3?characterEncoding=UTF-8"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置aop-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<aop:aspect id="txAdvice" ref="txManager">
<!--配置前置通知开启事务-->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!--配置后置通知提交事务-->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知回滚事务-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知释放连接-->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
测试类:
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("嘿嘿嘿","ccc",100f);
}
}
pom文件配置:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>acount</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!--spring IOC坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--spring整合单元测试坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--数据库控制工具QueryRunner-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<!--mysql连接工具-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--数据源-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--单元测试类-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
</project>
基于注解的AOP使用:
实体类一致:
持久层实现类于类上方加入@Repository(“beanId”),注入的bean上方使用@Autowired注解,根据类型注入,去除set注入方法。
修改部分代码如下
持久层实现类:
/**
* 账户的持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
//注解自动注入
@Autowired
private QueryRunner runner;
//注解自动注入连接工具类对象
@Autowired
private ConnectionUtils connectionUtils;
业务层实现类:
/**
* 账户的业务层实现类
*
* 事务控制应该都是在业务层
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
//注解自动注入持久层类对象
@Autowired
private IAccountDao accountDao;
工具类:
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
//将连接工具类作为bean加入spring容器
@Component("connectionUtils")
public class ConnectionUtils {
//新建ThreadLocal对象
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
//注入数据源对象
@Autowired
private DataSource dataSource;
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
@Component("txManager")
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
//声明切点的路径
//针对impl包下的包含任意参数的任意类的任意方法
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){
}
/**
* 开启事务
* 方法执行前,调用
*/
@Before("pt1()")
public void beginTransaction(){
try {
System.out.println("start");
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
* 方法执行后调用
*/
@AfterReturning("pt1()")
public void commit(){
try {
System.out.println("提交事务");
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
@AfterThrowing("pt1()")
public void rollback(){
try {
System.out.println("回滚事务");
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
@After("pt1()")
public void release(){
try {
System.out.println("释放连接");
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
XML文件:
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.itheima"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db3?characterEncoding=UTF-8"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
测试类:
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("嘿嘿嘿","ccc",100f);
}
}
四、JdbcTemplate入门
1.1 JdbcTemplate简单使用
示例代码:
/**
* 账户实体类
*/
public class Account {
private Integer id;
private String name;
private Float money;
}
XML文件配置:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置spring内置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///db3?characterEncoding=UTF-8"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
测试:
public class JdbcTemplateDemo1 {
public static void main(String[] args) {
//获取容器
ApplicationContext as = new ClassPathXmlApplicationContext("bean.xml");
//获取bean
JdbcTemplate jb = (JdbcTemplate) as.getBean("jdbcTemplate");
//jb.execute("insert into account(name,money)values('ccc',1000) ");
//List<Account> accounts = jb.query("select * from account where money = ?", new AccountRowMapper(), 1000);
/**
* 正常开发中,通常使用BeanPropertyRowMapper<>(Xxx.class)
*/
List<Account> accounts = jb.query("select * from account where money = ?", new BeanPropertyRowMapper<>(Account.class), 1000);
for (Account account : accounts) {
System.out.println(account);
}
}
}
/**
* 定义account的封装策略
*/
class AccountRowMapper implements RowMapper<Account>{
/**
* 把结果集中的数据封装到account中,用spring将每个account加到集合中
* @param resultSet
* @param i
* @return
* @throws SQLException
*/
@Override
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
Account account = new Account();
account.setId(resultSet.getInt("id"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getFloat("money"));
return account;
}
}
1.2 jdbcTemplate在Dao中的使用
省略实体类
持久层接口代码:
/**
* jdbcTemplate在dao层的使用
*/
public interface AccountDao {
/**
* 通过id查询
* @param id
* @return
*/
Account findById(Integer id);
}
持久层实现类代码:
/**
* 账户持久层实现类
*/
public class AccountDaoImpl implements AccountDao {
//创建jdbcTemplate对象
private JdbcTemplate jdbcTemplate;
//使用set方式注入
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Account findById(Integer id) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<>(Account.class),id);
return accounts.isEmpty()? null : accounts.get(0);
}
}
测试:
/**
* 账户持久层实现类
*/
public class JdbcTemplateDemo2 {
public static void main(String[] args) {
ApplicationContext as = new ClassPathXmlApplicationContext("bean.xml");
AccountDao accountDao = (AccountDao)as.getBean("accountDao");
Account byId = accountDao.findById(1);
System.out.println(byId);
}
}
1.3 jdbcDaoSupport的使用
bean.xml文件配置数据源和数据库信息
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置spring内置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///db3?characterEncoding=UTF-8"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
/**
* 继承JdbcDaoSupport类
* 的账户持久层实现类
*/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public Account findById(Integer id) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?", new BeanPropertyRowMapper<>(Account.class), id);
return accounts.isEmpty()? null: accounts.get(0);
}
@Override
public Account findByName(String name) {
return null;
}
@Override
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
其他类基本一致,参考上方即可
1.4 基于xml文件方式配置声明式事务控制
实体类同上,持久层业务层采用set方式注入,持久层使用JdbcDaoSupport进行业务支持。核心是通过配置文件的方式实现声明式事务控制,XML配置文件如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>-->
<!--配置spring内置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///db3?characterEncoding=UTF-8"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置事务管理器
配置事务管理器
配置事务的通知
1.此时需要导入事务的约束 tx名称空间和约束,同时需要配置aop
2.使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3.配置AOP的通用切入点表达式
4.建立事务通知和切入点的对应关系
5。配置事务的属性:
在事务的tx:advice标签中配置
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性:
isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的隔离级别
read-only:用于指定事务是否只读,只有查询方法才能设置为true,默认值为false,表示读写
timeout:用于指定事务的超时时间,默认值为-1表示永不超时,如果指定了数值以秒为单位
propagation:用于指定事务的传播行为,默认值为required,表示一定会有事务,增删改的选择,查询方法可以用Supports
no-rollback-for="":表示产生某个异常不回滚,产生其他异常回滚
rollback-for="":表示产生某个特定的异常回滚其他异常不回滚
-->
<tx:attributes>
<!--全通配-->
<tx:method name="*" propagation="REQUIRED" read-only="false"></tx:method>
<!--用于查询,优先级更高-->
<tx:method name="find" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!--建立切入点表达式和事务的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
1.5 通过注解的方式实现声明式事务控制
与xml配置方式变动的包括,持久层,业务层注入的方式,持久层不能通过继承JdbcDaoSupport实现业务操作。变动代码如下:
dao层
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findById(Integer id) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<>(Account.class), id);
return accounts.isEmpty()? null: accounts.get(0);
}
service层:
@Service("accountService")
@Transactional//开启事务
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
bean.xml文件
bean.xml文件
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" 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/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>-->
<!--配置spring内置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///db3?characterEncoding=UTF-8"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--spring基于注解的声明式事务控制配置步骤
1、配置事务管理器
2、开启spring对注解事务的支持
3、在需要开启事务的地方加上@Transactional注解
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
在这里插入图片描述](https://img-blog.csdnimg.cn/20210309112655831.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTI4NTU0Mg==,size_16,color_FFFFFF,t_70)
1.6 纯注解方式实现声明式事务
将数据库信息,jdbcTemplate TransactionManager 通过配置类的形式创建对象,在使用注解注入到spring容器中,持久层,业务层同上,多了几个配置类
jdbc.properties数据库文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///db3?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root
JdbcConfig配置类:
/**
* 连接数据库相关配置类
*/
@Configuration
@PropertySource("jdbc.properties")//可以放在总配置类也可以放在子配置类
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean(name = "dataSource")
public DataSource getDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
TransactionManager事务配置类:
/**
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器你对象
* @param dataSource
* @return
*/
@Bean(name = "transactionManager")
public PlatformTransactionManager createTrans(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
SpringConfig总配置类:
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class,TransactionConfig.class})
//@PropertySource("jdbc.properties")
//开启注解支持
@EnableTransactionManagement
public class SpringConfig {
}