Spring学习笔记
Spring
概述
Spring是什么?
Spring是分层的Java SE/EE应用 full-stack轻量级开源框架,以
- IoC——Inverse Of Control:反转控制
- AOP——Aspect Oriented Programming:面向切面编程
为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。
Spring的优势
方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
降低JavaEE API的使用难度
Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。
Spring的体系结构
控制反转IOC
概念
程序耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。
在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
解决程序耦合的思路
当是我们讲解jdbc时,是通过反射来注册驱动的,代码如下:
Class.forName("com.mysql.jdbc.Driver");//此处只是一个字符串
此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除mysql的驱动jar包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。
同时,也产生了一个新的问题,mysql驱动的全限定类名字符串是在java类中写死的,一旦要改还是要修改源码。 解决这个问题也很简单,使用配置文件配置。
工厂模式解耦
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。 那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
IOC—控制反转
控制反转(Inversion Of Control,英文缩写为IOC),把创建对象的权利交给框架。它包括依赖注入(Dependency Injection ,简称DI)和依赖查找(Dependency Lookup)。
作用
削减计算机程序的耦合(解除我们代码中的依赖关系)。
基于XML的IOC配置
创建Maven项目,在pom.xml中引入依赖。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
编写接口和实现类
public interface IAccountService {
void saveAccount();
}
public class AccountServiceImpl implements IAccountService {
//此处的依赖关系有待解决
private IAccountDao accountDao = new AccountDaoImpl();
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
创建持久层接口和实现类
public interface IAccountDao {
// 保存账户
void saveAccount();
}
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("保存了账户");
}
}
在类的根路径下创建一个任意名称的xml文件(不能是中文)例:bean.xml,给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
http://www.springframework.org/schema/beans/spring-beans.xsd">
让spring管理资源,在配置文件中配置service和dao
<!--
bean标签:用于配置让spring创建对象,并且存入ioc容器之中
id属性:对象的唯一标识。
class属性:指定要创建对象的全限定类名
-->
<!-- 配置service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
<!-- 配置dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"/>
测试配置是否成功
public class Client {
public static void main(String[] args) {
//1.使用ApplicationContext接口,就是在获取spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据bean的id获取对象
IAccountService aService = (IAccountService) ac.getBean("accountService");
System.out.println(aService);
IAccountDao aDao = (IAccountDao) ac.getBean("accountDao");
System.out.println(aDao);
}
}
基于XML的IOC细节
BeanFactory 和ApplicationContext 的区别
BeanFactory 才是Spring 容器中的顶层接口。
ApplicationContext 是它的子接口。
BeanFactory 和ApplicationContext 的区别:
- 创建对象的时间点不一样。
- ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
- BeanFactory:什么使用什么时候创建对象。
ApplicationContext 接口的实现类
ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建spring 容器。它用来读取注解。
IOC中Bean标签和管理对象细节
Bean标签
作用
用于配置对象让spring来创建的。 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性
-
id:给对象在容器中提供一个唯一标识。用于获取对象。
-
class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
-
scope:指定对象的作用范围。
-
singleton :默认值,单例的.
-
prototype :多例的.
-
request :WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中.
-
session :WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中.
-
global session :WEB项目中,应用在Portlet环境.如果没有Portlet环境那么
-
globalSession相当于session.
-
init-method:指定类中的初始化方法名称。
-
destroy-method:指定类中销毁方法名称。
作用范围和生命周期
单例对象:scope="singleton" ,一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:
- 对象出生:当应用加载,创建容器时,对象就被创建了。
- 对象活着:只要容器在,对象一直活着。
- 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
多例对象:scope="prototype" 每次访问对象时,都会重新创建对象实例。
生命周期:
- 对象出生:当使用对象时,创建新的对象实例。
- 对象活着:只要对象在使用中,就一直活着。
- 对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。
实例化Bean的三种方式
第一种方式:使用默认无参构造函数 <!--在默认情况下: 它会根据默认无参构造函数来创建类对象。如果bean中没有默认无参构造函数,将会创建失败。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
第二种方式:spring管理静态工厂-使用静态工厂的方法创建对象
// 模拟一个静态工厂,创建业务层实现类
public class StaticFactory {
public static IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
<!-- 此种方式是:
使用StaticFactory类中的静态方法createAccountService创建对象,并存入spring容器
id属性:指定bean的id,用于从容器中获取
class属性:指定静态工厂的全限定类名
factory-method属性:指定生产对象的静态方法 -->
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="createAccountService"></bean>
第三种方式:spring管理实例工厂-使用实例工厂的方法创建对象
// 模拟一个实例工厂,创建业务层实现类 * 此工厂创建对象,必须现有工厂实例对象,再调用方法
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
<!-- 此种方式是: 先把工厂的创建交给spring来管理。 然后在使用工厂的bean来调用里面的方法 factory-bean属性:用于指定实例工厂bean的id。 factory-method属性:用于指定实例工厂中创建对象的方法。 -->
<bean id="instancFactory" class="com.itheima.factory.InstanceFactory"/>
<bean id="accountService" factory-bean="instancFactory" factory-method="createAccountService"/>
依赖注入DI
概念
依赖注入:Dependency Injection。它是spring框架核心IOC的具体实现。 我们的程序在编写时,通过控制反转,把对象的创建交给了spring,但是代码中不可能出现没有依赖的情况。ioc解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。 那这种业务层和持久层的依赖关系,在使用spring之后,就让spring来维护了。 简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
注入方式
构造函数注入
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
@Override
public void saveAccount() {
System.out.println(name+","+age+","+birthday);
}
}
<!-- 使用构造函数的方式,给service中的属性传值
要求: 类中需要提供一个对应参数列表的构造函数。
涉及的标签:
constructor-arg 属性:
index:指定参数在构造函数参数列表的索引位置
type:指定参数在构造函数中的数据类型
name:指定参数在构造函数中的名称 用这个找给谁赋值
上面三个都是找给谁赋值,下面两个指的是赋什么值的
value:它能赋的值是基本数据类型和String类型
ref:它能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="张三">
</constructor-arg> <constructor-arg name="age" value="18">
</constructor-arg> <constructor-arg name="birthday" ref="now">
</constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
set方法注入
顾名思义,就是在类中提供需要注入成员的set方法。具体代码如下:
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public void saveAccount() {
System.out.println(name+","+age+","+birthday);
}
}
<!-- 通过配置文件给bean中的属性传值:使用set方法的方式
涉及的标签: property
属性:
name:找的是类中set方法后面的部分
ref:给属性赋值是其他bean类型的
value:给属性赋值是基本数据类型和string类型的 实际开发中,此种方式用的较多。 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="name" value="test"></property> <property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
名称空间注入数据
使用p名称空间注入数据(本质还是调用set方法),此种方式是通过在xml中导入p名称空间,使用p:propertyName来注入数据,它的本质仍然是调用类中的set方法实现注入功能。
/** * 使用p名称空间注入,本质还是调用类中的set方法 */
public class AccountServiceImpl4 implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override public void saveAccount() {
System.out.println(name+","+age+","+birthday);
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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">
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl4" p:name="test" p:age="21" p:birthday-ref="now"/> </beans>
注入集合属性
顾名思义,就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组,List,Set,Map,Properties。具体代码如下:
public class AccountServiceImpl implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
@Override
public void saveAccount() {
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
<!-- 注入集合数据 List结构的: array,list,set Map结构的 map,entry,props,prop -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!-- 给数组注入数据 -->
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- 注入list集合数据 -->
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array> </property>
<!-- 注入set集合数据 -->
<property name="mySet">
<list> <value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list> </property>
<!-- 注入Map数据 -->
<property name="myMap">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
<!-- 注入properties数据 -->
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
</bean>
IOC完成CRUD
需求和技术要求
需求:实现账户的CRUD操作
技术要求:
- 使用spring的IoC实现对象的管理
- 使用DBAssit作为持久层解决方案
- 使用c3p0数据源
环境搭建
创建Maven项目,并导入Jar包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</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.12</version>
</dependency>
</dependencies>
创建数据库和编写实体类
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
}
持久层代码
public interface IAccountDao {
void save(Account account);
void update(Account account);
void delete(Integer accountId);
Account findById(Integer accountId);
List<Account> findAll();
}
public class AccountDaoImpl implements IAccountDao {
private DBAssit dbAssit;
public void setDbAssit(DBAssit dbAssit) {
this.dbAssit = dbAssit;
}
@Override
public void save(Account account) {
dbAssit.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney()); }
@Override
public void update(Account account) {
dbAssit.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
@Override
public void delete(Integer accountId) {
dbAssit.update("delete from account where id=?",accountId);
}
@Override
public Account findById(Integer accountId) {
return dbAssit.query("select * from account where id=?",new BeanHandler<Account>(Account.class),accountId);
}
@Override
public List<Account> findAll() {
return dbAssit.query("select * from account where id=?",new BeanListHandler<Account>(Account.class));
}
}
业务层代码
public interface IAccountService {
void saveAccount(Account account);
void updateAccount(Account account);
void deleteAccount(Integer accountId);
Account findAccountById(Integer accountId);
List<Account> findAllAccount();
}
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount(Account account) {
accountDao.save(account);
}
@Override
public void updateAccount(Account account) {
accountDao.update(account);
}
@Override public void deleteAccount(Integer accountId) {
accountDao.delete(accountId);
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findById(accountId);
}
@Override public List<Account> findAllAccount() {
return accountDao.findAll();
}
}
创建并编写配置文件
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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean> <!-- 配置dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dbAssit" ref="dbAssit"></property>
</bean>
<!-- 配置dbAssit 此处我们只注入了数据源,表明每条语句独立事务-->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</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:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
测试类代码
public class AccountServiceTest {
@Test
public void testSaveAccount() {
Account account = new Account();
account.setName("黑马程序员");
account.setMoney(100000f);
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
as.saveAccount(account);
}
@Test
public void testFindAccountById() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
Account account = as.findAccountById(1); System.out.println(account);
}
@Test
public void testUpdateAccount() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
Account account = as.findAccountById(1); account.setMoney(20301050f);
as.updateAccount(account);
}
@Test public void testDeleteAccount() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
as.deleteAccount(1);
}
@Test public void testFindAllAccount() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
List<Account> list = as.findAllAccount();
for(Account account : list) {
System.out.println(account);
}
}
}
基于注解的IOC配置
创建Maven项目,并导入Jar包
<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>
@Component注解配置管理的资源
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
}
@Component("accountDao")
public class AccountDaoImpl implements IAccountDao {
private DBAssit dbAssit;
}
当我们使用注解注入时,set方法不用写
创建配置文件
spring的xml配置文件并开启对注解的支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 告知spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置dbAssit -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</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:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
纯注解配置
基于注解的IoC配置上面已经完成,但是大家都发现了一个问题:我们依然离不开spring的xml配置文件,那么能不能不写这个bean.xml,所有配置都用注解来实现呢? 当然,同学们也需要注意一下,我们选择哪种配置的原则是简化开发和配置方便,而非追求某种技术。
待改造的问题
我们发现,之所以我们现在离不开xml配置文件,是因为我们有一句很关键的配置:
<!-- 告知spring框架在,读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
如果他要也能用注解配置,那么我们就离脱离xml文件又进了一步。
另外,数据源和JdbcTemplate的配置也需要靠注解来实现。
<!-- 配置dbAssit -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</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:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
新注解说明
@Configuration
- 作用: 用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解。获取容器时需要使用AnnotationApplicationContext(有@Configuration注解的类.class)。
- 属性: value:用于指定配置类的字节码
// spring的配置类,相当于bean.xml文件
@Configuration
public class SpringConfiguration {
}
注意: 我们已经把配置文件用类来代替了,但是如何配置创建容器时要扫描的包呢? 请看下一个注解。
@ComponentScan
- 作用: 用于指定spring在初始化容器时要扫描的包。作用和在spring的xml配置文件中的: <context:component-scan base-package="com.itheima"/>是一样的。
- 属性: basePackages:用于指定要扫描的包。和该注解中的value属性作用一样。
// spring的配置类,相当于bean.xml文件
@Configuration
@ComponentScan("com.itheima")
public class SpringConfiguration {
}
@Bean
- 作用: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入spring容器。
- 属性: name:给当前@Bean注解方法创建的对象指定一个名称(即bean的id)。
public class JdbcConfig {
// 创建一个数据源,并存入spring容器中
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setUser("root");
ds.setPassword("1234");
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql:///spring_day02");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 创建一个DBAssit,并且也存入spring容器中
@Bean(name="dbAssit")
public DBAssit createDBAssit(DataSource dataSource) { return new DBAssit(dataSource); } }
注意: 我们已经把数据源和DBAssit从配置文件中移除了,此时可以删除bean.xml了。
但是由于没有了配置文件,创建数据源的配置又都写死在类中了。如何把它们配置出来呢? 请看下一个注解。
@PropertySource
- 作用:用于加载.properties文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到properties配置文件中,就可以使用此注解指定properties配置文件的位置。
- 属性: value[]:用于指定properties文件位置。如果是在类路径下,需要写上classpath:
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;
// 创建一个数据源,并存入spring容器中
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
jdbc.properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/day44_ee247_spring
jdbc.username=root jdbc.password=1234
jdbc.password=1234
注意: 此时我们已经有了两个配置类,但是他们还没有关系。如何建立他们的关系呢? 请看下一个注解。
@Import
- 作用: 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration注解。当然,写上也没问题。
- 属性: value[]:用于指定其他配置类的字节码。
@Configuration
@ComponentScan(basePackages = "com.itheima.spring")
@Import({ JdbcConfig.class})
public class SpringConfiguration {
}
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig{
}
我们已经把要配置的都配置好了,但是新的问题产生了,由于没有配置文件了,如何获取容器呢? 请看下一小节。
通过注解获取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
常用注解
用于创建对象的
相当于::
@Component
- 作用: 把资源让spring来管理。相当于在xml中配置一个bean。
- 属性: value:指定bean的id。如果不指定value属性,默认bean的id是当前类的类名。首字母小写。
@Controller @Service @Repository
他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。
他们只不过是提供了更加明确的语义化。
- @Controller:一般用于表现层的注解。
- @Service:一般用于业务层的注解。
- @Repository:一般用于持久层的注解。
细节:如果注解中有且只有一个属性要赋值时,且名称是value,value在赋值是可以不写。
用于注入数据的
相当于:
@Autowired
作用:
自动按照类型注入。当使用注解注入属性时,set方法可以省略。它只能注入其他bean类型。当有多个类型匹配时,使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功。找不到就报错。
@Qualifier
- 作用: 在自动按照类型注入的基础之上,再按照Bean的id注入。它在给字段注入时不能独立使用,必须和@Autowire一起使用;但是给方法参数注入时,可以独立使用。
- 属性: value:指定bean的id。
@Resource
- 作用: 直接按照Bean的id注入。它也只能注入其他bean类型。
- 属性: name:指定bean的id。
@Value
- 作用: 注入基本数据类型和String类型数据的
- 属性: value:用于指定值
用于改变作用范围的
相当于:
@Scope
- 作用: 指定bean的作用范围。
- 属性: value:指定范围的值。 取值:singleton prototype request session globalsession
和生命周期相关的
相当于:
@PostConstruct
作用: 用于指定初始化方法。
@PreDestroy
作用: 用于指定销毁方法。
整合Junit
问题
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
为什么不把测试类配到xml中
在解释这个问题之前,先解除大家的疑虑,配到XML中能不能用呢?
答案是肯定的,没问题,可以使用。
那么为什么不采用配置到xml中的方式呢?
这个原因是这样的:
- 当我们在xml中配置了一个bean,spring加载配置文件创建容器时,就会创建对象。
- 测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。 所以,基于以上两点,我们不应该把测试配置到xml文件中。
解决思路分析
针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建spring容器,我们就无须手动创建了,问题也就解决了。
我们都知道,junit单元测试的原理(在web阶段课程中讲过),但显然,junit是无法实现的,因为它自己都无法知晓我们是否使用了spring框架,更不用说帮我们创建spring容器了。不过好在,junit给我们暴露了一个注解,可以让我们替换掉它的运行器。
这时,我们需要依靠spring框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需要告诉它配置文件在哪就行了。
配置步骤
第一步
创建Maven项目,pom.xm导入依赖。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</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.12</version>
</dependency>
</dependencies>
第二步
使用@RunWith注解替换原有运行器
@RunWith(SpringJUnit4ClassRunner.class)
public class AccountServiceTest {
}
第三步
使用@ContextConfiguration指定spring配置文件的位置
@ContextConfiguration注解:
- locations属性:用于指定配置文件的位置。如果是类路径下,需要用classpath:表明
- classes属性:用于指定注解的类。当不使用xml配置时,需要用此属性指定注解类的位置。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:bean.xml"})
public class AccountServiceTest {
}
第四步
使用@Autowired给测试类中的变量注入数据
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:bean.xml"})
public class AccountServiceTest {
@Autowired
private IAccountService as ;
}
面向切面编程AOP
AOP:全称是Aspect Oriented Programming即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
AOP的作用及优势
- 作用: 在程序运行期间,不修改源码对已有方法进行增强。
- 优势: 减少重复代码 提高开发效率 维护方便
AOP实现方式
使用动态代理技术
动态代理实现AOP
问题
数据库事务处理
对数据库进行CUD操作时,需要手动提交事务、发生异常时需要回滚,且每个Dao或者Service中的CUD方法都需要写一遍,此时项目里充斥着很多重复代码,并且业务层和事务控制方法耦合了。
解决办法
动态代理技术
-
基于接口的动态代理——Proxy
- 提供者:JDK官方的Proxy类。
- 要求:被代理类最少实现一个接口。
-
基于子类的动态代理——CGLib
- 提供者:第三方的CGLib,如果报asmxxxx异常,需要导入asm.jar。
- 要求:被代理类不能用final修饰的类(最终类)。
Proxy动态代理
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
如何创建代理对象:使用Proxy类中的newProxyInstance方法
创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法的参数:
- ClassLoader:类加载器它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
- Class[]:字节码数组它是用于让代理对象和被代理对象有相同方法。固定写法。
- InvocationHandler:用于提供增强的代码
- 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。
使用JDK官方的Proxy类创建代理对象
// 对生产厂家要求的接口
public interface IProducer {
// 销售
public void saleProduct(float money);
// 售后
public void afterService(float money);
}
// 一个生产者
public class Producer implements IProducer{
// 销售
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}
// 售后
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
// 模拟一个消费者
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.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 returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
}
Enhancer动态代理
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
如何创建代理对象:使用Enhancer类中的create方法
创建代理对象的要求:被代理类不能是最终类(final修饰)
create方法的参数:
- Class:字节码,它是用于指定被代理对象的字节码。
- Callback:用于提供增强的代码它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
我们一般写的都是该接口的子接口实现类:MethodInterceptor
// 一个生产者
public class Producer {
// 销售
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}
// 售后
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
// 模拟一个消费者
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行北地阿里对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}
Spring中的AOP
AOP相关术语
- Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
- Pointcut(切入点): 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
- Advice(通知/增强): 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
- Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。 Target(目标对象): 代理的目标对象。
- Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。 spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
- Proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类。
- Aspect(切面): 是切入点和通知(引介)的结合。
代理的选择
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
基于XML的AOP配置
项目准备
可以使用“动态代理实现AOP”那个项目继续。
导入依赖
在pom.xml导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
创建配置文件
创建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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
配置类
// 用于记录日志的工具类,它里面提供了公共的代码
public class Logger {
// 前置通知
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
// 后置通知
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
// 异常通知
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
// 最终通知
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
// 账户的业务层接口
public interface IAccountService {
// 模拟保存账户
void saveAccount();
// 模拟更新账户
void updateAccount(int i);
// 删除账户
int deleteAccount();
}
// 账户的业务层实现类
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("执行了保存");
// int i=1/0;
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
配置IOC
配置Spring的IOC
<!-- 配置srping的Ioc,把service对象配置进来-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
配置AOP
使用aop:config声明aop配置
execution:匹配方法的执行(常用)
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
<!--配置AOP-->
<aop:config>
<!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行
<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
<!-- 配置环绕通知 详细的注释请看Logger类中-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
测试AOP
// 测试AOP的配置
public class AOPTest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
}
}
JdbcTemplate
概述
它是Spring框架中提供的一个对象,是对原始Jdbc API对象的简单封装。Spring框架为我们提供了很多的操作模板类。
操作关系型数据的:
- JdbcTemplate
- HibernateTemplate
操作nosql数据库的:
- RedisTemplate
操作消息队列的:
- JmsTemplate
我们今天的主角在spring-jdbc-5.0.2.RELEASE.jar中,我们在导包的时候,除了要导入这个jar包外,还需要导入一个spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。
CRUD
前期准备
创建数据库:
create database spring_day02;
use spring_day02;
创建表:
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;
创建项目
创建Maven项目,pom.xml中配置依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
JavaBean
创建实体类、Dao接口和实现类
// 账户的实体类
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
get set toString略
}
// 账户的持久层接口
public interface IAccountDao {
// 根据Id查询账户
Account findAccountById(Integer accountId);
// 根据名称查询账户
Account findAccountByName(String accountName);
// 更新账户
void updateAccount(Account account);
}
Spring配置文件
在resources文件夹下创建bean.xml的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">
<!-- 配置一个数据库的操作模板:JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring_day02"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
</beans>
最基本使用
public class JdbcTemplateDemo2 {
public static void main(String[] args) {
//1.获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
//3.执行操作
jt.execute("insert into account(name,money)values('eee',500)");
}
}
保存
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
//1.获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
//3.执行操作
//保存
jt.update("insert into account(name,money)values(?,?)","fff",5000);
}
}
更新
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
//1.获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
//3.执行操作
//修改
jt.update("update account set money = money-? where id = ?",300,6);
}
}
删除操作
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
//1.获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
//3.执行操作
//删除
jt.update("delete from account where id = ?",6); } }
查询所有
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
//1.获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
//3.执行操作
//查询所有
List<Account> accounts = jt.query("select * from account where money > ? ", new AccountRowMapper(), 500);
for(Account o : accounts){
System.out.println(o);
}
}
}
public class AccountRowMapper implements RowMapper<Account>{
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}
查询一个
使用RowMapper的方式:常用的方式
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
//1.获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
//3.执行操作
//查询一个
List<Account> as = jt.query("select * from account where id = ? ", new AccountRowMapper(), 55);
System.out.println(as.isEmpty()?"没有结果":as.get(0));
}
}
使用ResultSetExtractor的方式:不常用的方式
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
//1.获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
//3.执行操作
//查询一个
Account account = jt.query("select * from account where id = ?", new AccountResultSetExtractor(),3);
System.out.println(account);
}
}
查询返回一行一列
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
//1.获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
//3.执行操作
//查询返回一行一列:使用聚合函数,在不使用group by字句时,都是返回一行一列。最长用的就是分页中获取总记录条数
Integer total = jt.queryForObject("select count(*) from account where money > ? ",Integer.class,500);
System.out.println(total);
}
}
在DAO中使用
准备实体类
/** * 账户的实体 */
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
get set toString略
}
两种方式
- 第一种在Dao类中定义JdbcTemplate的方式,适用于所有配置方式(xml和注解都可以)。
- 第二种让Dao继承JdbcDaoSupport的方式,只能用于基于XML的方式,注解用不了。
第一种方式
在dao中定义JdbcTemplate
// 账户的接口
public interface IAccountDao {
// 根据id查询账户信息
Account findAccountById(Integer id);
// 根据名称查询账户信息
Account findAccountByName(String name);
// 更新账户信息
void updateAccount(Account account);
}
// 账户的持久层实现类 * 此版本的dao,需要给dao注入JdbcTemplate
public class AccountDaoImpl implements IAccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Account findAccountById(Integer id) {
List<Account> list = jdbcTemplate.query("select * from account where id = ? ",new AccountRowMapper(),id);
return list.isEmpty()?null:list.get(0);
}
@Override
public Account findAccountByName(String name) {
List<Account> list = jdbcTemplate.query("select * from account where name = ? ",new AccountRowMapper(),name);
if(list.isEmpty()){
return null;
}
if(list.size()>1){
throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
} return list.get(0);
}
@Override public void updateAccount(Account account) {
jdbcTemplate.update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
}
}
配置文件
<?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">
<!-- 配置一个dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入jdbcTemplate -->
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 配置一个数据库的操作模板:JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring_day04"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
此时有个问题:当我们的dao有很多时,每个dao都有一些重复性的代码。下面就是重复代码:
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
能不能把它抽取出来呢?看第二种方式
第二种方式
让dao继承JdbcDaoSupport,JdbcDaoSupport是spring框架为我们提供的一个类,该类中定义了一个JdbcTemplate对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源:具体源码如下:
public abstract class JdbcDaoSupport extends DaoSupport {
//定义对象
private JdbcTemplate jdbcTemplate;
//set方法注入数据源,判断是否注入了,注入了就创建
JdbcTemplate public final void setDataSource(DataSource dataSource) {
if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
//如果提供了数据源就创建
JdbcTemplate this.jdbcTemplate = createJdbcTemplate(dataSource);
initTemplateConfig();
}
}
//使用数据源创建JdcbTemplate
protected JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
//当然,我们也可以通过注入
JdbcTemplate对象 public final void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
initTemplateConfig();
}
//使用getJdbcTmeplate方法获取操作模板对象
public final JdbcTemplate getJdbcTemplate() {
return this.jdbcTemplate;
}
Account接口
public interface IAccountDao {
// 根据id查询账户信息
Account findAccountById(Integer id);
// 根据名称查询账户信息
Account findAccountByName(String name);
// 更新账户信息
void updateAccount(Account account);
}
// 账户的持久层实现类 * 此版本dao,只需要给它的父类注入一个数据源
public class AccountDaoImpl2 extends JdbcDaoSupport implements IAccountDao {
@Override
public Account findAccountById(Integer id) {
//getJdbcTemplate()方法是从父类上继承下来的。
List<Account> list = getJdbcTemplate().query("select * from account where id = ? ",new AccountRowMapper(),id);
return list.isEmpty()?null:list.get(0);
}
@Override
public Account findAccountByName(String name) {
//getJdbcTemplate()方法是从父类上继承下来的。
List<Account> list = getJdbcTemplate().query("select * from account where name = ? ",new AccountRowMapper(),name);
if(list.isEmpty()){
return null;
} if(list.size()>1){ throw new RuntimeException("结果集不唯一,不是只有一个账户对象"); } return list.get(0);
}
@Override
public void updateAccount(Account account) {
//getJdbcTemplate()方法是从父类上继承下来的。
getJdbcTemplate().update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
}
}
配置文件
<?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">
<!-- 配置dao2 -->
<bean id="accountDao2" class="com.itheima.dao.impl.AccountDaoImpl2">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring_day04"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
事务控制
第一:JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
第二:spring框架为我们提供了一组事务控制的接口。具体在后面的第二小节介绍。这组接口是在spring-tx-5.0.2.RELEASE.jar中。
第三:spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。
API介绍
PlatformTransactionManager
此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法
获取事务状态信息
TransactionStatus getTransaction(TransactionDefinitiondefinition)
提交事务
void commit(TransactionStatus status)
回滚事务
void rollback(T ransactionStatus status)
我们在开发中都是使用它的实现类,如下:
使用SpringJDBC或iBatis进行持久化数据时使用
org.springframework.jdbc.datasource.DataSourceTransactionManager
使用Hibernate版本进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager
TransactionDefinition
获取事务对象名称
String getName()
获取事务隔离级
int getlsolationLevel()
获取事务传播行为
int getPropagationBehavior()
获取事务超时时间
int getTimeout()
获取事务是否只读
boolean isReadOnly() 读写型事务:增加、删除、修改开启事务。只读型事务:执行查询时,也会开启事务
TransactionStatus
TransactionStatus接口描述了某个时间点上事务对象的状态信息,包含有6个具体的操作:
- void flush(),刷新事务
- boolean hasSavepoint(),获取是否是否存在存储点
- boolean isCompleted(),获取事务是否完成
- boolean isNewTransaction(),获取事务是否为新的事务
- boolean isRollbackOnly(),获取事务是否回滚
- void setRollbackOnly(),设置事务回滚
事务隔离级别
事务隔离级反映事务提交并发访问时的处理态度
ISOL ATION_ DEFAULT,默认级别,归属下列某一种
ISOL ATION READ UNCOMMITTED,可以读取未提交数据
ISOL ATION READ COMMITTED,只能读取已提交数据,解决脏读问题(Oracle默认级别)
ISOLATION REPEATABLE READ,是否读取其他事务提交修改后的数据,解决不可重复读问题(MySQL默认级别)
ISOL ATION SERIALIZABLE,是否读取其他事务提交添加后的数据,解决幻影读问题
事务的传播行为
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。
超时时间
默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
是否是只读事务
建议查询时设置为只读。
声明式事务配置
创建Maven项目,pom.xml导入依赖。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</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.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
Spring配置文件
创建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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
准备数据库和实体
创建数据库:
create database spring_day04;
use spring_day04;
创建表:
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
get set ToString 略
}
业务层接口和实现类
/**
* 账户的业务层接口
*/
public interface IAccountService {
/**
* 根据id查询账户信息
* @param id
* @return
*/
Account findAccountById(Integer id);
//查
/**
* 转账
* @param sourceName 转出账户名称
* @param targeName 转入账户名称
* @param money 转账金额
*/
void transfer(String sourceName,String targeName,Float money);
}
/** * 账户的业务层实现类 */
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public Account findAccountById(Integer id) {
return accountDao.findAccountById(id);
}
@Override
public void transfer(String sourceName, String targeName, Float money) {
//1.根据名称查询两个账户
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targeName);
//2.修改两个账户的金额
source.setMoney(source.getMoney()-money);
//转出账户减钱
target.setMoney(target.getMoney()+money);
//转入账户加钱
//3.更新两个账户
accountDao.updateAccount(source);
int i=1/0;
accountDao.updateAccount(target);
}
}
Dao接口和实现类
// 账户的持久层接口
public interface IAccountDao {
// 根据id查询账户信息
Account findAccountById(Integer id);
// 根据名称查询账户信息
Account findAccountByName(String name);
/** * 更新账户信息 * @param account */
void updateAccount(Account account);
}
/** * 账户的持久层实现类 * 此版本dao,只需要给它的父类注入一个数据源 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public Account findAccountById(Integer id) {
List<Account> list = getJdbcTemplate().query("select * from account where id = ? ",new AccountRowMapper(),id);
return list.isEmpty()?null:list.get(0);
}
@Override
public Account findAccountByName(String name) {
List<Account> list = getJdbcTemplate().query("select * from account where name = ? ",new AccountRowMapper(),name);
if(list.isEmpty()){
return null;
} if(list.size()>1){
throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
}
return list.get(0);
}
@Override
public void updateAccount(Account account) {
getJdbcTemplate().update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
}
}
/** * 账户的封装类RowMapper的实现类*/
public class AccountRowMapper implements RowMapper<Account>{
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}
配置文件中配置业务层和持久层对
<!-- 配置service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring_day04"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
配置事务管理器
<!-- 配置一个事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入DataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
配置事务的通知引用事务管理器
<!-- 事务的配置 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager"> </tx:advice>
配置事务的属性
<!--在tx:advice标签内部 配置事务的属性 -->
<tx:attributes>
<!--
指定方法名称:是业务核心方法
read-only:是否是只读事务。默认false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。 -->
<tx:method name="*" read-only="false" propagation="REQUIRED"/> <tx:method name="find*" read-only="true" propagation="SUPPORTS"/> </tx:attributes>
配置AOP切入点表达式
<!-- 配置aop -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/>
</aop:config>
配置切入点表达式和事务通知的对应关系
<!-- 在aop:config标签内部:建立事务的通知和切入点表达式的关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
注解事务配置
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</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.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<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>
<!-- 配置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://localhost:3306/spring_day02"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启spring对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
database.mysql
创建数据库:
create database spring_day04;
use spring_day04;
创建表:
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;
AccountServiceImpl
/** * 账户的业务层实现类 */
@Service("accountService")
@Transactional(readOnly=true,propagation=Propagation.SUPPORTS) // 配置事务
public class AccountServiceImpl implements IAccountService {
@Autowired private IAccountDao accountDao;
//其余代码和基于XML的配置相同
}
@Transactional
该注解的属性和xml中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持 出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
AccountDaoImpl
创建Dao接口和实现类并使用注解让spring管理
/** * 账户的持久层实现类 */
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//其余代码和基于XML的配置相同
}
不使用xml的配置方式
@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
//里面配置数据源,配置JdbcTemplate,配置事务管理器。在之前的步骤已经写过了。
}