最近复习ssm,翻出之前的笔记并整理了一下,想要源码可以私聊,没有上传(写的比较凌乱🤐)
Mybatis
Mybatis框架概述
Mybatis
是一个基于java
的持久层框架,内部封装了jdbc
,使开发者只需要关注SQL语句本身,无需要花费精力去处理加载驱动,创建连接,创建Statement
等复杂过程。Mybatis
通过XML或者注解的方式将要执行的各种statement
配置起来,并通过java对象和statement
中的SQL
的动态参数进行映射,生成最终需要执行的SQL语句,最后由mybatis
框架执行SQL
并将结果映射为java
对象返回。- 使用
mybatis
配置之后就不用再写dao
接口的实现类了(通过动态代理的方式创建实体类) - ORM:
Object Relational Mapping
对象关系映射
传统JDBC使用 VS Mybatis
传统JDBC指的是使用JDK自带的数据库连接库,使用方式包括如下步骤:注册驱动
,获取连接
,预处理
,执行SQL语句
,遍历结果集
,释放资源
Connection
PerparedStatement
ResultSet
Mybatis操作步骤
- 持久层接口——
IUserDao
mybatis
的主配置文件
——MyBatis-config.xml
(配置环境、指定映射配置文件的位置)映射配置文件
的约束——IUserDao.xml
(映射配置文件的mapper标签namespace属性的取值必须是dao
接口的全限定类名
)
public static void main(String[] args) throws IOException {
//1.读取配置文件
InputStream io= Resources.getResourceAsStream("MyBatis-config.xml");
//2.创建SqlSessionFactory工厂,因为接口不能直接new,所以借助SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
SqlSessionFactory factory=builder.build(io);
//3.使用工厂生产SqlSession对象
SqlSession session=factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象,因为此接口没有实现类,所以使用代理的方式对接口进行增强
IUserDao userDao=session.getMapper(IUserDao.class);
//5.使用代理对象执行方法
List<User> users=userDao.findAll();
for (User user: users) {
System.out.println(user);
}
//6.释放资源
session.close();
io.close();
}
MyBatis还支持注解开发,只需要mybatis基于注解的入门案例
- 把
IUserDao.xml
移除,在dao
接口的方法上使用@Select
注解,并且指定SQL语句 - 同时需要在
MyBatis-config.xml
中的mapper
配置时,使用class
属性指定dao接口的全限定类名
配置文件参数
-
parameterType
(参数)SQL语句传参,使用 parameterType标签属性来设置要传入参数的属性。参数属性可以是基本类型,引用类型、实体类类型(POJO 类)、实体类的包装类 POJO(Plain Ordinary Java Object)
-
resultType
(结果)resultType 属性可以指定拿到结果集的类型,它支持基本类型和实体类类型
-
resultMap
resultMap
使用之前需要先定义;如果遵循ORM
思想,则不必使用resultMap
Mybatis的连接池技术
- 在 Mybatis 的主配置文件中(
MyBatis-config.xml
), 通过<dataSource type=”pooled”>
来实现 Mybatis中连接池的配置。
Mybatis的事务控制
在 JDBC
中我们可以通过手动方式将事务的提交改为手动方式,即通过 setAutoCommit()
方法就可以调整,参数为True则为自动提交,参数为False则为手动提交。 Mybatis
框架是对 JDBC
的封装,所以 Mybatis
框架的事务控制方式,本身也是用 JDBC
的setAutoCommit()
方法来设置事务提交方式的。 JDBC事务:
- 在
JDBC
中处理事务,都是通过Connection
完成的。同一事务中所有的操作,都在使用同一个Connection
对象。JDBC
事务默认是开启的,并且是默认提交。 JDBC Connection
接口提供了两种事务模式:自动提交和手工提交。JDBC
中的事务与java.sql.Connection
中的三个方法有关:
setAutoCommit(boolean)
:设置是否为自动提交事务,每条执行的SQL语句都是一个单独的事务,如果设置为false,需要手动提交事务。commit()
:提交结束事务。rollback()
:回滚结束事务。
mybati
s中更改事务提交方式使用public SqlSession openSession(boolean autoCommit)
,默认为自动提交方式,设置为false则为手动提交
mybatis的动态SQL
注意:判断的是我们传入的参数值是否满足条件,满足条件就进行SQL语句查询。
<if test="userName!=null">
<where>
<foreach collection="ids" open="id in (" close=")" item="id" separator=","> #{id}</foreach>
- 引入抽取的代码
<include refid="default"></include>
mybatis多表查询
多对一
- 继承实体类
- 从表实体包含主表实体的引用方式(常用)
一对多
- 主表实体包含从表实体的集合引用
多对多
- 多对多可以拆分为两个一对多
Mybatis延迟加载
有时候我们在加载用户信息时不一定非要加载它的账户信息,这是候就要用到延时加载
概念:MyBatis
中的延迟加载也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select
查询。在真正使用数据的时候才发起查询,不用的时候不查询关联的数据,延迟加载又叫做按需查询。
MyBatis
中延迟加载的条件:resultMap
可以实现高级映射,例:使用association
、collection
实现一对一及一对多映射,association
、collection
具备延迟加载功能。
Mybatis 缓存机制
mybatis为减轻数据库压力,提高数据库性能。提供了两级缓存机制:
-
一级缓存:
SqlSession
级别的缓存,缓存的数据只在SqlSession
内有效。 一级缓存mybatis
已近为我们自动开启,不用我们手动操作,而且我们是关闭不了的!!但是我们可以手动清除缓存。一级缓存是sqlSession
级别的缓存。在操作数据库时需要构造sqlSession
对象,在对象中有一个基于 PerpetualCache
的HashMap
本地缓存数据结构,用于缓存数据。不同的sqlSession
之间的缓存数据区域(HashMap
)是互不影响的。
查询同样的数据,mybatis
会先去sqlsession
中,查询是否有,有的话直接拿出来用。 当SqlSession
对象消失时( flush
或 close
),mybatis
的一级缓存也就消失了。
在不关闭session的情况下测试两次取得相同结果是否为相同对象。
User user1=userDao.findUserById(1);
System.out.println(user1);
User user2=userDao.findUserById(1);
System.out.println(user2);
//返回true就证明是同一个对象
System.out.println(user1==user2);
验证一级缓存什么情况下消失
一级缓存是 SqlSession
范围的缓存,当调用 SqlSession
的修改,添加,删除, commit()
((执行插入、更新、删除)), close()
等方法时,就会清空一级缓存。
//第一种方法
//关闭session
sqlSession.close();
//重新获取session
sqlSession= factory.openSession();
userDao=sqlSession.getMapper(IUserDao.class);
//第二种方法
sqlSession.clearCache();
// 第三种 更新用户,再次查询
userDao.updateUser(user1);
、、、
//返回false就证明是一级缓存被释放
System.out.println(user1==user2);
注意:两次查询须在同一个
sqlsession
中完成,否则将不会走mybatis
的一级缓存。在mybatis
与spring
进行整合开发时,事务控制在service
中进行,重复调用两次servcie
将不会走一级缓存,因为在第二次调用时session
方法结束,SqlSession
就关闭了
-
二级缓存:
mapper
级别的缓存,同一个namespace
公用这一个缓存,所以对SqlSession
是共享的。 二级缓存需要我们手动开启。(全局级别) 二级缓存是mapper
级别的缓存,多个sqlSession
去操作同一个Mapper
的sql语句,多个sqlSession
可以共用二级缓存,二级缓存是跨sqlSession
的。
当一个sqlseesion
执行了一次select
后,关闭此session
的时候,会将查询结果缓存到二级缓存 当另一个sqlsession
执行select
时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能
注意事项:
如果
SqlSession
执行了DML
操作(insert、update、delete
),并commit
了,那么mybatis
就会清空当前mapper
缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读mybatis
的缓存是基于[ namespace:sql语句:参数 ]
来进行缓存的,意思就是,SqlSession
的HashMap
存储缓存数据时,是使用[ namespace:sql:参数 ]
作为key,查询返回的语句作为value保存的。
开启mybatis二级缓存
通过application.yml配置二级缓存开启
# mybatis相关配置
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#开启MyBatis的二级缓存
cache-enabled: true
通过MyBatis配置文件开启二级缓存【在MyBatis-config.xml 文件中添加如下代码】
<setting name="cacheEnabled" value="true"/>
在 xxxMapper.xml 文件中添加
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"/>
<select id="findUserById" parameterType="INT" resultType="user" useCache="true">
- 二级缓存中存放的是数据,不是对象,所以每次每次从二级缓存中查数据都会创建一个新的对象把数据封装起来,返回的对象虽然不是一个但是数据是相同的
- 一级缓存中存放的是对象,每次返回的对象都是相同的
测试二级缓存
//3.获取SqlSession对象
SqlSession sqlSession1=factory.openSession();
//4.获取dao的代理对象
IUserDao userDao1=sqlSession1.getMapper(IUserDao.class);
User user1=userDao1.findUserById(41);
System.out.println(user1);
//5.释放资源
sqlSession1.close();
SqlSession sqlSession2=factory.openSession();
IUserDao userDao2=sqlSession2.getMapper(IUserDao.class);
User user2=userDao2.findUserById(41);
System.out.println(userDao2);
sqlSession2.close();
System.out.println(user1==user2);
Mybatis基于注解的开发
Mybatis的常用注解
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
使用注解方式配置持久层接口
/*查询所有用户*/
@Select("select * from user")
//实现对结果集封装的配置,和xml中的resultMap标签功能一样
@Results(id="userMap",
value= {
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
@Result(column="sex",property="userSex"),
@Result(column="address",property="userAddress"),
@Result(column="birthday",property="userBirthday")
})
List<User> findAll();
/*根据 id 查询一个用户*/
@Select("select * from user where id = #{uid} ")
//实现引用@Results 定义的封装
@ResultMap("userMap")
User findById(Integer userId);
/*保存操作*/
//先保存后查询
@Insert("insert into user(username, sex, birthday, address)values(#{userName},#{userSex},#{userBirthday},#{userAddress})")
@SelectKey(keyColumn = "id", keyProperty = "userId", resultType = Integer.class, before = false, statement = {"select last_insert_id()"})
int saveUser(User user);
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的体系结构
耦合:程序间的依赖关系
- 类之间的依赖(A类调用或者继承B类,A类就依赖B类)
- 方法间的依赖(A方法调用B方法,A方法就依赖B方法)
解耦: 降低程序间的依赖关系
实际开发中: 应该做到编译期不依赖,运行时才依赖。
- 使用反射来创建对象,而避免使用new关键字。
- 通过读取配置文件来获取要创建的对象全限定类名
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
,如果使用这种方式注册JDBC
驱动(使用new
关键字),编译期就会产生依赖,如果我们把pom文件里面的mysql依赖注释掉,就会立马爆红,更没办法编译。 Class.forName("com.mysql.jdbc.Driver");
,使用这种方式(类加载器),编译期就不会产生依赖,如果我们把pom
文件里面的mysql
依赖注释掉,也不会立马爆红,但是无法编译。
IoC
工厂模式解耦
我们把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候, 让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。那么,这个读取配置文件, 创建和获取三层对象的类就是工厂,这种方法就称为:工厂模式解耦;这种被动接收的方式获取对象的思想就是控制反转,他是spring框架的核心之一。
IoC(Inversion Of Control)控制反转
IoC控制反转,我们将创建对象的过程交给工厂,在我们需要创建对象的时候由工厂反射来为我们创建对象,控制权转交给工厂(控制反转)
使用工厂模式解耦演示
bean.properties配置文件
用于反射创建对象
key=accountDao
value=com.hong.dao.impl.AccountDaoImpl
key=accountService
value=com.hong.service.impl.AccountServiceImpl
工厂类BeanFactory
public class BeanFactory {
//1.定义一个Properties对象(用来读取配置文件)
private static Properties props;
//2.定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//3.实例化对象
props = new Properties();
//4.获取properties文件的流对象,使用类加载器来获取,避免路径问题
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
//5.将配置文件内容加载到Properties对象中
props.load(in);
//6.实例化容器
beans = new HashMap<String,Object>();
//7.取出配置文件中所有的Key
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//8.取出每个Key
String key = keys.nextElement().toString();
// System.out.println(key);
//9.根据key获取value
String beanPath = props.getProperty(key);
//System.out.println(beanPath);
//10.反射创建对象:使用类加载器,把map中的value属性
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/*根据bean的名称获取对象*/
//注意此处的返回值类型应该是一个Object类型(不能为具体的类型,反射创建的类型不是一个)
public static Object getBean(String beanName){
//System.out.println(beans.get("accountService"));
return beans.get(beanName);
}
}
/**
* 模拟一个表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//使用new关键字创建对象(我们要解决的就是不用new关键字,使用反射创建对象)
//IAccountService as = new AccountServiceImpl();
//使用反射创建对象
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
as.saveAccount();
}
}
使用 spring 的 IOC 解决程序耦合(基于xml配置文件)
beans.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">
<!-- bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中
id 属性:对象的唯一标识。 class 属性:指定要创建对象的全限定类名-->
<!-- 配置 service -->
<bean id="accountService" class="com.hong.service.impl.AccountServiceImpl">
</bean>
<!-- 配置 dao -->
<bean id="accountDao" class="com.hong.dao.impl.AccountDaoImpl"></bean>
</beans>
模拟表现层Client
- 获取
Spring
的ioc
核心容器,并根据id
获取对象 ApplicationContext
的三个常用实现类ClassPathXmlApplicationContext
:加载类路径下的配置文件(我们使用这个)FileSystemXmlApplicationContext
:加载任意路径下的配置文件AnnotationConfigApplicationContext
:读取注解创建容器- 核心容器两个接口引发的问题:
ApplicationContext
:(单例模式比较好),他在构建容器时,创建对象采取的策略是采用立即加载的方式,也就是说,只要一读取完配置文件, 马上就创建配置文件中配置的对象BeanFactory
:(多例模式比较好)他在构建容器时,创建对象的策略是采用延时加载的方式,也就是说,什么时候根据id
获取对象,什么时候真正创建对象
**
* 模拟一个表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//1.获取spring核心容器中的对象
ApplicationContext ac=new ClassPathXmlApplicationContext("beans.xml");
//2.根据id获取bean对象
IAccountService as=(IAccountService)ac.getBean("accountService");
as.saveAccount();
}
}
Spring基于XML的IOC细节
创建bean的三种方式
第一种方式:使用默认构造函数创建。 在spring
的配置文件中使用bean
标签,配以id
和class
属性之后,且没有其他属性和标签时。采用的就是默认构造函数创建bean
对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="AccountServiceImpl"></bean>
第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<bean id="accountService" class="StaticFactory" factory-method="getAccountService"></bean>
bean对象的作用范围
bean
的作用范围调整:使用bean
标签的scope
属性,用于指定bean的作用范围 取值: 常用的就是单例的和多例
singleton
:单例的(默认值)prototype
:多例的request
:作用于web
应用的请求范围session
:作用于web
应用的会话范围global-session
:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
(和负载均衡有关)init-method
: 指定类中的初始化方法名称。destroy-method
: 指定类中销毁方法名称。
bean对象的生命周期
Bean定义--> 实例化 --> 填充属性 --> 初始化 --> 生存期 --> 销毁
- 单例对象 出生:当容器创建时对象出生 活着:只要容器还在,对象一直活着 死亡:容器销毁,对象消亡
单例对象的生命周期和容器相同
- 多例对象 出生:当我们使用对象时
spring
框架为我们创建 活着:对象只要是在使用过程中就一直活着。 死亡:当对象长时间不用,且没有别的对象引用时,由Java
的垃圾回收器回收
spring 的依赖注入
依赖注入: Dependency Injection
。 它是 spring
框架核心 ioc
的具体实现。 我们的程序在编写时, 通过控制反转,把对象的创建交给spring
来管理,但是代码中不可能出现没有依赖的情况。ioc
解耦只是降低他们的依赖关系,但不会消除。 例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系, 在使用 spring
之后, 就让 spring
来维护了。 简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取
依赖注入能注入的数据
- 基本类型和
String
- 其他
bean
类型(在配置文件中或者注解配置过的bean
) - 复杂类型/集合类型
依赖注入的方式
- 使用构造函数提供
- 使用
set
方法提供 - 使用注解提供
<?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">
<!--构造函数注入:AccountServiceImpl类-->
<bean id="accountService" class="com.hong.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="泰斯特"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
<!-- set方法注入:AccountServiceImpl2类-->
<bean id="accountService2" class="com.hong.service.impl.AccountServiceImpl2">
<property name="name" value="TEST" ></property>
<property name="birthday" ref="now"></property>
</bean>
<!-- 复杂类型的注入/集合类型的注入-->
<bean id="accountService3" class="com.hong.service.impl.AccountServiceImpl3">
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
</set>
</property>
<property name="myProps">
<map>
<entry key="testC" value="CCC"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
</bean>
</beans>
spring基于注解的 IOC配置
创建对象的注解常用注解
-
在xml中:
<bean id="" class="">
是用于创建bean对象的配置,在注解中对应@Component
注解 -
@Component
的衍生注解,功能和属性都和@Component
注解一样,这三个注解存在的意义就是便于区分所创建的bean类型@Controller
: 一般用于表现层的注解。@Service
: 一般用于业务层的注解。@Repository
: 一般用于持久层的注解。
注入数据的常用注解
- 在xml中:
<property name="" ref="">
和<property name="" value="">
用于给bean注入数据,在注解中对应@Autowired
注解
自动按照类型注入。 如果容器中只有唯一的一个
bean
对象类型和要注入的变量类型匹配,就一定可以注入成功 如果容器中有多个bean
对象类型和要注入的变量类型匹配,首先按照数据类型找到所有匹配的bean
对象,然后使用变量名称作为Id
继续查找
@Qualifier
: 对于多个bean
时结合AutoWired
注解一起使用,在给类成员注入时不能单独使用要和AutoWired
一起使用,一个用于查找数据类型,一个用于查找变量名称。@Resource
: 直接按照bean
的id
注入。
上面三个注入都只能注入其他bean
类型的数据,而基本类型和String
类型无法使用上述注解实现。另外,集合类型的注入只能通过XML
来实现
- 使用
@Value
注入基本类型和String
类型(value
:用于指定数据的值。它可以使用spring
中SpEL
(也就是spring
的el
表达式)SpEL
的写法: ${表达式} )
改变作用范围的注解@Scope
在xml中:<bean id="" class="" scope="">
用于改变作用的范围, 在注解中对应:@Scope
注解
生命周期相关的注解 @PostConstruct和@PreDestroy
spring使用纯注解配置
xml加注解的方式来配置spring
<!-- 1.告知spring框架在创建容器时扫描注解所扫描的包-->
<context:component-scan base-package="com.aismall"></context:component-scan>
<--2.数据源和 JdbcTemplate 的配置也需要靠注解来实现。-->
<!-- 配置 dbAssit -->
<bean id="dbAssit" class="com.aismall.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:///mySpring"></property>
<property name="user" value="root"></property>
<property name="password" value="12345678"></property>
</bean>
用于指定一个类为配置类的注解@Configuration
用于指定当前类是一个 spring
配置类, 当创建容器时会从该类上加载注解。 获取容器时需要使用AnnotationApplicationContext(有@Configuration 注解的类.class)
。
指定初始化容器时要扫描的包的注解@ComponentScan
用于指定 spring 在初始化容器时要扫描的包。 作用和在 spring
的 xml
配置文件中的:<context:component-scan base-package="com.hong"/>
是一样的。
配置数据源和 JdbcTemplate 对象的注解@Bean
该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring
容器。name
:给当前@Bean
注解方法创建的对象指定一个名称(即 bean
的 id
)
public class JdbcConfig {
/**
* 创建一个数据源,并存入 spring 容器中
*/
public class JdbcConfig {
@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:///mySpring");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 创建一个 DBAssit,并且也存入 spring 容器中
*/
@Bean(name="dbAssit")
public DBAssit createDBAssit(DataSource dataSource) {
return new DBAssit(dataSource);
}
}
解决创建数据源的配置写死在类中的问题的注解@PropertySource
用于加载.properties
文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到properties
配置文件中,就可以使用此解指定 properties
配置文件的位置。 属性:value[]
用于指定 properties
文件位置。如果是在类路径下,需要写上 classpath
连接配置类的注解@Import
用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration
注解。 当然,写上也没问题 当我们使用Import
的注解之后,有Import
注解的类就父配置类, 而导入的都是子配置类 属性: value[]
用于指定其他配置类的字节码。
基于注解开发下的配置主配置类
/*创建Bean对象,当配置类作为AnnotationConfigApplicationContext对象创建的参数时,
该注解可以不写*/
//@Configuration
//指定要扫描的包
@ComponentScan("com.aismall")
//导入其他配置类
@Import(JdbcConfig.class)
//指定properties文件的位置
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfig {
}
/**
* 和spring连接数据库相关的配置类
*/
//配置文件注解
@Configuration
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;
/**
* 用于创建一个QueryRunner对象
*/
/*创建一个QueryRunner对象*/
//把当前方法的返回值作为bean对象存入spring的ioc容器中
@Bean(name = "runner")
//默认单例,我们需要多例
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
*/
@Bean(name="ds")
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);
}
}
}
Spring整合Junit
测试类里面没有整合junit
之前,不能用@Autowired
自动注入,得用容器生成,影响代码可读性, junit
集成了一个main
方法 ,该方法就会判断当前测试类中哪些方法有 @Test
注解,然后junit
就让有Test
注解的方法执行
junit
不会管我们是否采用spring
框架 ,在执行测试方法时,junit
根本不知道我们是不是使用了spring
框架,所以也就不会为我们读取配置文件/配置类创建spring
核心容器
解决过程
导入Spring的测试jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
// 使用@RunWith 注解替换原有运行器
@RunWith(SpringJUnit4ClassRunner.class)
// 使用@ContextConfiguration 指定 spring 配置文件的位置
@ContextConfiguration(locations= {"classpath:bean.xml"})
public class AccountServiceTest {
// 使用@Autowired 给测试类中的变量注入数据
@Autowired
private IAccountService as ;
}
AOP
使用面向对象编程 ( OOP
)有一些弊端,当需要为多个不具有继承关系的对象引人同一个公共行为时,例如日志、安全检测等,我们只有在每个对象里引用公共行为,这样程序中就产生了大量的重复代码,程序就不便于维护了。
所以就有了一个对面向对象编程的补充,即面向方面编程 ( AOP
), AOP
所关注的方向是横向的,区别于 OOP
的纵向。
AOP的引出
下面来看一个转帐的事务案列。
aop概述
什么是面向方面编程,3个过程:
- 找到横切点:首要目标确定在程序的哪个位置进行横切逻辑
- 横切逻辑(业务代码):横切逻辑代码,这个就是横切业务代码,与aop无关
- 织入:将横切逻辑织入到横切点
AOP相关术语
-
Joinpoint
(连接点): 所谓连接点是指那些被拦截到的点。在spring
中,这些点指的是方法,因为spring
只支持方法类型的连接点。通俗的说就是被代理类中的所有方法 -
Pointcut
(切入点): 所谓切入点是指我们要对哪些Joinpoint
进行拦截的定义 通俗的说就是被代理类中的被代理的方法,因为被代理类中并不是所有的方法都被代理了 -
Advice
(通知/增强): 所谓通知是指拦截到Joinpoint
之后所要做的事情就是通知。 通俗的说就是对被代理的方法进行增强的代码- 前置通知:在被代理方法执行之前执行
- 后置通知:在被代理方法执行之后执行
- 异常通知:在被代理方法执行出错的时候执行
- 最终通知:无论怎样都会执行
注意:后置通知和异常通知只能有一个会被执行,因为发生异常执行异常通知,然后就不会继续向下执行,自然后置通知也就不会被执行,反之亦然
Introduction
(引介): 引介是一种特殊的通知在不修改类代码的前提下,Introduction
可以在运行期为类动态地添加一些方法或Field
。Target
(目标对象): 代理的目标对象, 通俗的说就是被代理的对象Weaving
(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。spring
采用动态代理织入,而AspectJ
采用编译期织入和类装载期织入。 通俗的说就是让增强的代码(通知)植入到待增强的方法(切入点)中Proxy
(代理) : 一个类被AOP
织入增强后,就产生一个结果代理类。Aspect
(切面): 是切入点和通知(引介)的结合 ,通俗的说就是建立切入点和通知方法在创建时的对应关系
作用、优势
在程序运行期间,不修改源码对已有方法进行增强。减少重复代码、提高开发效率、维护方便。
-
开发阶段(我们做的)
- 编写核心业务代码(前期),
- 把公用代码抽取出来,制作成通知(后期)(把公共的代码通过增强的的方式织入到方法中)
- 在配置文件中,声明切入点与通知间的关系,即切面(重点)
-
运行阶段(
Spring
框架完成的)Spring
框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行
关于代理的选择
- 动态代理分为两种:基于接口的动态代理和基于子类的动态代理(
cglib
) - 当代理类实现了某个接口的时候,使用基于接口的动态代理
- 当代理类没有实现任何接口的时候,使用基于子类的动态代理(
cglib
)
基于 XML 的 AOP 配置
AOP配置标签详解
aop:config
标签:表明开始AOP
的配置aop:aspect
标签表明配置切面,id
属性:是给切面提供一个唯一标识(可随意指定,一般都只指定有意义的名字);ref
属性:是指定通知类bean
的Id
aop:before
:表示配置前置通知;method
属性:用于指定通知类中哪个方法是前置通知;pointcut
属性:用于指定切入点表达式,该表达式的含义指的是对哪些方法进行增强。
切入点表达式的写法: 关键字:
execution
(表达式) 表达式:访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
标准的表达式写法范例:public void com.hong.service.impl.AccountServiceImpl.saveAccount()
- 访问修饰符可以省略
- 返回值可以使用通配符
*
,表示任意返回值- 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个
*.
- 包名可以使用
..
表示当前包及其子包- 类名和方法名都可以使用
*
来实现通配- 参数列表:可以直接写数据类型: 基本类型直接写名称 :int 引用类型写包名.类名的方式 :java.lang.String 可以使用通配符表示任意类型,但是必须有参数 可以使用
…
表示有无参数均可,有参数可以是任意类型
全通配写法:* *..*.*(..)
实际开发中切入点表达式的通常写法,切到业务层实现类下的所有方法:* com.hong.service.impl.*.*(..)
- aop:after-returning:表示配置后置通知
- aop:after-throwing:表示配置异常通知
- aop:after :表示配置最终通知
注意:这几种通知里面的写法都是相同的,配置完之后通知出现的顺序不同。
aop:pointcut 标签
- 作用:结合
aop:XXX
使用,简化配置 - 可以再
aop:aspect
标签内部使用,与aop:XXX
标签同级,这样就只当前切面可用 - 也可以在
aop:config
标签内使用,与aop:aspect
标签同级,这样aop:config
标签内的所有切面都可以使用
<!--配置AOP-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.hong.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="advice" ref="Advice">
<!-- 配置前置通知:在切入点方法执行之前执行-->
<aop:before method="beforeAdvice" pointcut-ref="pt1" ></aop:before>
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturningAdvice" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
<aop:after method="afterAdvice" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
aop:around标签(环绕通知一般单独使用)
- 环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.
- 对于环绕通知来说, 连接点的参数类型必须是
ProceedingJoinPoint
. 它是JoinPoint
的子接口, 允许控制何时执行, 是否执行连接点. - 在环绕通知中需要明确调用
ProceedingJoinPoint
的proceed()
方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行. - 注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用
joinPoint.proceed()
; 的返回值, 否则会出现空指针异常
/**
* 通知类,它里面提供了公共的代码
*/
public class Advice {
/**
* 前置通知
*/
public void beforeAdvice(){
System.out.println("前置通知Advice类中的beforeAdvice方法执行了。。。");
}
/**
* 后置通知
*/
public void afterReturningAdvice(){
System.out.println("后置通知Advice类中的afterReturningAdvice方法开执行了。。。");
}
/**
* 异常通知
*/
public void afterThrowingAdvice(){
System.out.println("异常通知Advice类中的afterThrowingAdvice方法执行了。。。");
}
/**
* 最终通知
*/
public void afterAdvice(){
System.out.println("最终通知Advice类中的afterAdvice方法执行了。。。");
}
/**
* 环绕通知
*/
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("通知类中的aroundAdvice方法执行了。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("通知类中的aroundAdvice方法执行了。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("通知类中的aroundAdvice方法执行了。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("通知类中的aroundAdvice方法执行了。。最终");
}
}
}
bean.xml
<!-- 配置advice通知类 -->
<bean id="advice" class="com.aismall.utils.Advice"></bean>
<!--配置AOP-->
<aop:config>
<!--为了使所有切面都可以使用,此标签配置在外面-->
<aop:pointcut id="pt1" expression="execution(* com.aismall.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="advice" ref="advice">
<!-- 配置前置通知:在切入点方法执行之前执行-->
<aop:before method="beforeAdvice" pointcut-ref="pt1"></aop:before>
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturningAdvice" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
<aop:after method="afterAdvice" pointcut-ref="pt1"></aop:after>
<!-- 配置环绕通知-->
<!--<aop:around method="aroundAdvice" pointcut-ref="pt1"></aop:around>-->
</aop:aspect>
</aop:config>
运行结果
前置通知Advice类中的beforeAdvice方法执行了。。。
执行了保存
后置通知Advice类中的afterReturningAdvice方法开执行了。。。
最终通知Advice类中的afterAdvice方法执行了。。。
JdbcTemplate
JdbcTemplate 概述
spring
框架中提供的一个对象,是对原始 JDBC API
对象的简单封装。 spring
框架为我们提供了很多的操作模板类。
通过查看JdbcTemplate
的源码可知,除了默认构造函数之外,其他的构造函数都需要提供一个数据源。既然有set
方法,我们就可以使用依赖注入,首先我们要在配置文件中配置这些对象
对比下面这两句,发现
spring
中JdbcTemplate
的结果的返回值并不是由BeanPropertyRowMapper
指定,而是由query
方法(被多次重载)的返回值决定,这一点和DBUtils
中的ResultSetHandler
不一样DBUtils
中的返回结果由ResultSetHandler
决定,而不是query
方法的返回值。
List<Account> accounts=jdbcTemplate.query("select * from account",new BeanPropertyRowMapper<Account>(Account.class));
Account account = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
spring中的事务管理(transactionManager)
spring支持两种方式的事务管理
一、编程式事务管理
通过 TransactionTemplate
或者TransactionManager
手动管理事务,实际应用中很少使用
二、声明式事务管理
实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)。
Spring事务管理接口介绍
Spring
框架中,事务管理相关最重要的 3 个接口如下:
PlatformTransactionManager
: (平台)事务管理器,Spring 事务策略的核心。TransactionDefinition
: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。TransactionStatus
:事务运行状态 我们可以把PlatformTransactionManager
接口可以被看作是事务上层的管理者,而TransactionDefinition
和TransactionStatus
这两个接口可以看作是事务的描述。
PlatformTransactionManager
会根据 TransactionDefinition
的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus
接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
PlatformTransactionManager
:事务管理接口
Spring
并不直接管理事务,而是提供了多种事务管理器 。Spring
事务管理器的接口是:PlatformTransactionManager
。
通过这个接口,Spring
为各个平台如 JDBC
(DataSourceTransactionManager
)、Hibernate
(HibernateTransactionManager
)、JPA
(JpaTransactionManager
)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
将事务管理行为抽象出来,然后不同的平台去实现它,这样我们可以保证提供给外部的行为不变,方便我们扩展
public interface PlatformTransactionManager {
//获得事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
TransactionDefinition:事务属性
事务管理器接口 PlatformTransactionManager
通过 getTransaction(TransactionDefinition definition)
方法来得到一个事务,这个方法里面的参数是 TransactionDefinition
类 ,这个类就定义了一些基本的事务属性。
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
// 返回事务的传播行为,默认值为 REQUIRED。
int getPropagationBehavior();
//返回事务的隔离级别,默认值是 DEFAULT
int getIsolationLevel();
// 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
int getTimeout();
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();
@Nullable
String getName();
}
TransactionStatus:事务状态
TransactionStatus
接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。
PlatformTransactionManager.getTransaction(…)
方法返回一个 TransactionStatus
对象。
public interface TransactionStatus{
// 刷新事务
void flush();
boolean isNewTransaction(); // 是否是新的事务
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}
事务的隔离级别
事务的四种特性(ACID)
事务的隔离级别分五种
1,ISOLATION_DEFAULT
:这是一个,默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应;
2,ISOLATION_READ_UNCOMMITTED
:这是事务最低的隔离级别,它允许别外一个事务可以看到这个事务未提交的数据。 这种隔离级别会产生脏读,不可重复读和幻像读。
3,ISOLATION_READ_COMMITTED
:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
4,ISOLATION_REPEATABLE_READ
:这种事务隔离级别可以防止脏读,不可重复读。 但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
5,ISOLATION_SERIALIZABLE
:这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读。
事务的传播行为
REQUIRED
:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)SUPPORTS
:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)MANDATORY
:使用当前的事务,如果当前没有事务,就抛出异常REQUERS_NEW
:新建事务,如果当前在事务中,把当前事务挂起。NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起NEVER
:以非事务方式运行,如果当前存在事务,抛出异常NESTED
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
事务超时属性
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition
中以 int
的值来表示超时时间,其单位是秒,默认值为-1。
事务只读属性
只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
事务回滚规则
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常(RuntimeException
的子类)时才会回滚,Error
也会导致事务回滚,但是,在遇到检查型(Checked
)异常时不会回滚。
spring的事务控制配置(XML)
1、配置事务管理器 2、配置事务的通知 此时我们需要导入事务的约束 tx
名称空间和约束,同时也需要aop
的 使用tx:advice
标签配置事务通知(属性:id
:给事务通知起一个唯一标识 transaction-manager
:给事务通知提供一个事务管理器引用) 3、配置AOP
中的通用切入点表达式 4、建立事务通知和切入点表达式的对应关系 5、配置事务的属性,是在事务的通知tx:advice标签的内部
<?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">
<!-- 配置业务层-->
<bean id="accountService" class="com.aismall.service.impl.AccountServiceImpl_old">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.aismall.dao.impl.AccountDaoImpl_tx">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 第一步:配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第二步:配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 第五步:配置事务的属性-->
<!--配置处理不同事务所用的方法-->
<!--全统配符优先级小于部分通配符的优先级-->
<tx:attributes>
<!--处理增删改的方法:一定会发生事务,读写-->
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<!--处理查询的方法:不一定发生事务,只读-->
<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.aismall.service.impl.*.*(..))"></aop:pointcut>
<!--第四步:建立切入点表达式和事务通知的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
@Transactional事务注解原理
@Transactional
的工作机制是基于 AOP
实现的,AOP
又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK
的动态代理,如果目标对象没有实现了接口,会使用 CGLIB
动态代理。createAopProxy()
方法 决定了是使用 JDK 还是 Cglib 来做动态代理,源码如下:
如果一个类或者一个类中的 public
方法上被标注@Transactional
注解的话,Spring
容器就会在启动的时候为其创建一个代理类,在调用被@Transactional
注解的 public
方法的时候,实际调用的是,TransactionInterceptor
类中的 invoke()
方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。
基于注解的事务控制配置
<!--配置事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
开启事务的注解驱动
通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就
是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
Spring AOP自调用问题
如果同一个类中的其他没有@Transactional
注解的方法内部调用有@Transactional
注解的方法,有@Transactional
注解的方法的事务会失效。
这是由于Spring AOP
代理的原因造成的,因为只有当 @Transactional
注解的方法在类以外被调用的时候,Spring
事务管理才生效。
解决办法就是避免同一类中自调用或者使用
AspectJ
取代Spring AOP
代理。
@Transactional 的使用注意事项总结
@Transactional
注解只有作用到public
方法上事务才生效,不推荐在接口上使用; 2)避免同一个类中调用@Transactional
注解的方法,这样会导致事务失效; 3)正确的设置@Transactional
的rollbackFor
和propagation
属性,否则事务可能会回滚失败; 4)被@Transactional
注解的方法所在的类必须被Spring
管理,否则不生效; 5)底层使用的数据库必须支持事务机制,否则不生效。
三层结构和MVC
那么在
B/S
架构中,系统标准的三层架构包括:表现层、业务层、持久层。 表现层:也就是我们常说的web
层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http
协议请求web
层, web
需要接收 http
请求,完成 http
响应。
表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。 表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端。 表现层的设计一般都使用 MVC
模型。(MVC
是表现层的设计模型,和其他层没有关系)
业务层:也就是我们常说的 service
层。它负责业务逻辑处理,和我们开发项目的需求息息相关。
web
层依赖业务层,但是业务层不依赖 web
层。 业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说的,事务应该放到业务层来控制) 持久层:也就是我们是常说的 dao
层,负责数据持久化,包括数据层即数据库和数据访问层。
数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库中。
MVC模型
MVC
是表现层的设计模型,和其他层没有关系。
MVC
全名是 Model View Controller
,是模型(model
)-视图(view
)-控制器(controller
)的缩写,是一种用于设计创建 Web
应用程序表现层的模式。
MVC
中每个部分各司其职:
Model
(模型):通常指的就是我们的数据模型。作用一般情况下用于封装数据。(JavaBean
)
View
(视图):通常指的就是我们的 jsp
或者 html
。作用一般就是展示数据的。通常视图是依据模型数据创建的。
Controller
(控制器):是应用程序中处理用户交互的部分。 作用一般就是处理程序逻辑的。它相对于前两个不是很好理解,这里举个例子:
常见的MVC
框架包括:SpringMVC
和Struts2
等。Spring
为我们提供的web
前端开发框架SpringMVC
SpringMVC的优势
1、清晰的角色划分:
- 前端控制器(
DispatcherServlet
) - 处理器映射器(
HandlerMapping
) - 处理器适配器(
HandlerAdapter
) - 视图解析器(
ViewResolver
) - 处理器或控制器(
Controller
) - 验证器(
Validator
) - 命令对象(
Command
请求参数绑定到的对象就叫命令对象) - 表单对象(
Form Object
提供给表单展示和提交到的对象就叫表单对象)。
2、分工明确,而且扩展点相当灵活,可以很容易扩展
3、由于命令对象就是一个 POJO
,无需继承框架特定 API
,可以使用命令对象直接作为业务对象。
4、和 Spring
其他框架无缝集成,是其它 Web
框架所不具备的。
5、可适配,通过 HandlerAdapter
可以支持任意的类作为处理器。
6、可定制性, HandlerMapping
、 ViewResolver
等能够非常简单的定制。
7、功能强大的数据验证、格式化、绑定机制。
8、利用 Spring
提供的 Mock
对象能够非常简单的进行 Web
层单元测试。
9、本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换等等。
配置核心控制器,一个 Servlet,这个servlet在webapp下的web.xml
中配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 第一步:配置 spring mvc 的核心控制器 -->
<servlet>
<!-- dispatcherServlet:前端控制器,由SpringMVC框架提供 -->
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 第二步:配置初始化参数,用于读取 SpringMVC 的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--第三步:配置 servlet 的对象的创建时间点:
应用加载时创建,取值只能是非 0 正整数,表示启动顺序 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
SpringMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 第四步:配置创建 spring 容器要扫描的包 -->
<context:component-scan base-package="com.aismall"></context:component-scan>
<!--第五步: 配置视图解析器 -->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/pages/"></property>
<!-- 后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
编写控制器并使用注解配置, 启动服务器测试。
执行过程
补充
servlet
知识
Servlet
的主要功能在于交互式地浏览和修改数据,生成动态Web
内容。这个过程为:
- 客户端发送请求至服务器端;
- 服务器将请求信息发送至
Servlet
;Servlet
生成响应内容并将其传给服务器。响应内容动态生成,通常取决于客户端的请求;- 服务器将响应返回给客户端。
1、当启动Tomcat
服务器的时候,web.xml
配置文件被加载,因为在web.xml
文件中配置了load-on-startup
标签,所以会创建dispatcherServlet
对象,此时就会加载springmvc.xml
配置文件
2,加载springmvc.xml
文件后,就会通过springmvc.xml
文件中的配置创建 spring 容器并且初始化容器中的对象
3、浏览器发送请求,被 dispatherServlet
捕获,该 Servlet
并不处理请求,而是把请求转发出去。转发的路径是根据请求 URL
,匹配@RequestMapping
中的内容。
4、匹配到了后,执行对应方法。该方法有一个返回值。
5、根据方法的返回值,借助视图解析器internalResourceViewResolver
找到对应的结果视图。
6、渲染结果视图,响应浏览器。
DispatcherServlet:前端控制器
前端控制器控制整个流程的执行,类似一个控制中心
,它就相当于 mvc 模式中的 c, 由它调用其它组件
处理用户的请求, dispatcherServlet 的存在降低了组件之间的耦合性。
HandlerMapping:处理器映射器
HandlerMapping 负责根据用户请求找到
那个类中那个方法来执行(映射)
处理器映射器就是:根据我们的 URL
寻找 Handler
SpringMVC
提供了不同的映射器实现不同的映射方式 例如:配置文件方式,实现接口方式,注解方式等
HandlAdapter:处理器适配器
处理器适配器就是:按照它要求的规则去执行 Handler
,我们上面的例子中没有用到这个处理器适配器
Handler或者称为Controller:处理器
它就是我们开发中要编写的具体业务控制器。由 dispatcherServlet
把用户请求转发到 Handler
。由Handler
对具体的用户请求进行处理。
View Resolver:视图解析器
View Resolver
负责将处理结果生成View
视图, View Resolver
首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View
视图对象,最后对 View
进行渲染将处理结果通过页面展示给用户。
View:视图
SpringMVC
框架提供了很多的 View
视图类型的支持,包括: jstlView、 freemarkerView、 pdfView等。我们最常用的视图就是 jsp
。
mvc:annotation-driven注解的作用
在 SpringMVC
的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC
的三大组件。
也可以在 SpringMVC.xml
配置文件中使用注解进行配置:mvc:annotation-driven替代配置处理映射器和处理适配器。
注意:此注解不能替代配置视图解析器,也就意味着视图解析器需要另外进行配置
RequestMapping注解的作用
作用:用于建立请求 URL
和处理请求方法之间的对应关系
RequestMapping
注解出现位置:类上,方法上
类上
:请求 URL 的第一级访问目录。此处不写的话,就相当于应用的根目录。 写的话需要以/开头。它出现的目的是为了使我们的 URL
可以按照模块化管理,使我们的 URL 更加精细。方法上
:请求 URL 的第二级访问目录。
params
:用于指定限制请求参数的条件。headers
:用于指定限制请求消息头的条件。
请求参数的绑定
绑定的机制
表单中请求参数都是基于 key=value
的,SpringMVC
绑定请求参数的过程是,通过把表单提交请求参数作为控制器中方法的参数进行绑定的。
支持的数据类型
- 基本类型参数:包括基本类型和
String
类型 POJO
类型参数:包括实体类,以及关联的实体类- 数组和集合类型参数:包括
List
结构和Map
结构的集合(包括数组)
SpringMVC 绑定请求参数是自动实现的,但是要想使用,必须遵循使用要求。
中文请求乱码问题
<!--配置解决中文乱码的过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 设置过滤器中的属性值 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<!-- 过滤所有请求 -->
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在 springmvc
的配置文件中可以配置,静态资源不过滤:
<!-- location 表示路径, mapping 表示文件, **表示该目录下的文件以及子目录的文件 -->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/scripts/" mapping="/javascript/**"/>
自定义类型转换器
定义一个自定义转换器类,实现 Converter
接口,该接口有两个泛型,S:表示接受的类型
, T:表示目标类型
/**
* 自定义类型转换器
*/
public class StringToDateConverter implements Converter<String, Date> {
/**
* 用于把 String 类型转成日期类型
*/
@Override
public Date convert(String source) {
DateFormat format = null;
try {
if(StringUtils.isEmpty(source)) {
throw new NullPointerException("请输入要转换的日期");
}
format = new SimpleDateFormat("yyyy-MM-dd");
Date date = format.parse(source);
return date;
} catch (Exception e) {
throw new RuntimeException("输入日期有误");
}
}
}
在 springmvc.xml
配置文件中配置类型转换器,将自定义的转换器注册到类型转换服务中去并在 annotation-driven
标签中引用配置的类型转换服务
<!--配置自定义类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.aismall.utils.StringToDateConverter"/>
</set>
</property>
</bean>
<!-- 引用自定义类型转换器 -->
<mvc:annotation-driven conversion-service="converterService">
注意:
</mvc:annotation-driven>
标签只会时处理器映射器和处理器适配器生效,不会使自定义类型转换器生效,所以我们通过<mvc:annotation-driven conversion-service="converterService">
配置来使自定义的类型转换器生效
常用注解
RequestParam注解
把请求中指定名称的参数给控制器中的形参赋值。
属性:
- value: 请求参数中的名称。
- required:请求参数中是否必须提供此参数。 默认值: true。表示必须提供,如果不提供将报错
RequestBody注解
用于获取请求体内容。 直接使用得到是 key=value&key=value...
结构的数据。get
请求方式不适用。
属性:required
:是否必须有请求体。默认值是:true
。当取值为 true
时,get
请求方式会报错。如果取值为 false
, get
请求得到是 null
。
PathVariable注解
用于绑定 url
中的占位符。例如:请求 url 中 /delete/{id}, 这个{id}就是 url占位符。 url
支持占位符是 spring3.0
之后加入的。是 springmvc
支持 rest
风格 URL
的一个重要标志。
属性:value
: 用于指定 url
中占位符名称。required
:是否必须提供占位符。
REST 风格的URL(Representational State Transfer)
状态转化(State Transfer) :
- 浏览器每发出一个请求,就代表了客户端和服务器的一次交互过程。
HTTP
协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段, 让服务器端发生“ 状态转化” (。而这种转化是建立在表现层之上的,所以就是 “ 表现层状态转化” 。- 具体说,就是 HTTP 协议里面,四个表示操作方式的动词:
GET
、POST
、PUT
、DELETE
。
RequestHeader注解
用于获取请求消息头。value
:提供消息头名称. required
:是否必须有此消息头
注:在实际开发中一般不怎么用
CookieValue注解
用于把指定 cookie
名称的值传入控制器方法参数。
ModelAttribute注解
该注解是 SpringMVC4.3
版本以后新加入的。它可以用于修饰方法和参数。 出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法。出现在参数上,获取指定的数据给参数赋值。
value
:用于获取数据的 key
。 key
可以是 POJO
的属性名称,也可以是 map
结构的 key。
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题
。
SessionAttribute注解
用于多次执行控制器方法间的参数共享。value
:用于指定存入的属性名称type
:用于指定存入的数据类型。
SpringMVC的数据响应
- 页面跳转
- 直接返回字符串
- 通过ModelAndView对象返回
- 回写数据
- 直接返回字符串
- 返回对象或集合
@RequestMapping(value="/quick3")
public ModelAndView save3(ModelAndView modelAndView){
/*
Model:模型 作用封装数据
View:视图 作用展示数据
*/
// ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
// return "success";
return modelAndView;
}
@RequestMapping(value="/quick4")
public String save4(Model model){
model.addAttribute("username","博学谷");
return "success";
}
@RequestMapping(value="/quick5")
public String save5(HttpServletRequest request){
request.setAttribute("username","酷丁鱼");
return "success";
}
SpringMVC的文件上传
引入jar包
<form action="${pageContext.request.contextPath}/user/quick22" method="post" enctype="multipart/form-data">
名称<input type="text" name="username"><br/>
文件1<input type="file" name="uploadFile"><br/>
<input type="submit" value="提交">
</form>
@RequestMapping(value="/quick")
@ResponseBody
public void save(String username, MultipartFile uploadFile) throws IOException {
//获得上传文件的名称
String originalFilename = uploadFile.getOriginalFilename();
uploadFile.transferTo(new File("C:\upload\"+originalFilename));
}
// 多文件上传
public void save23(String username, MultipartFile[] uploadFile) throws IOException {
System.out.println(username);
for (MultipartFile multipartFile : uploadFile) {
String originalFilename = multipartFile.getOriginalFilename();
multipartFile.transferTo(new File("C:\upload\"+originalFilename));
}
}
SpringMVC的拦截器
Spring MVC
的拦截器类似于 Servlet
开发中的过滤器 Filter
,用于对处理器进行预处理和后处理。
将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(InterceptorChain
)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP
思想的具体实现。
public class MyInterceptor1 implements HandlerInterceptor {
//在目标方法执行之前 执行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
System.out.println("preHandle.....");
return false;
}
//在目标方法执行之后 视图对象返回之前执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("postHandle...");
}
//在流程都执行完毕后 执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("afterCompletion....");
}
}
springmvc.xml
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--对哪些资源执行拦截操作-->
<mvc:mapping path="/**"/>
<bean class="com.itheima.interceptor.MyInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
当拦截器的preHandle
方法返回true
则会执行目标资源,如果返回false
则不执行目标资源
多个拦截器情况下,配置在前的先执行,配置在后的后执行拦截器中的方法执行顺序是:preHandler
-------目标资源----postHandle
---- afterCompletion
SpringMVC异常处理机制
系统中异常包括两类:预期异常和运行时异常RuntimeException
,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。
系统的Dao
、Service
、Controller
出现都通过throws Exception
向上抛出,最后由SpringMVC
前端控制器交由异常处理器进行异常处理
异常处理两种方式
① 使用Spring MVC
提供的简单异常处理器SimpleMappingExceptionResolver
② 实现Spring
的异常处理接口HandlerExceptionResolver
自定义自己的异常处理器
简单异常处理器SimpleMappingExceptionResolver
<!--配置简单映射异常处理器-->
<bean class=“org.springframework.web.servlet.handler.SimpleMappingExceptionResolver”>
<property name=“defaultErrorView” value=“error”/> # 默认错误视图
<property name=“exceptionMappings”>
<map>
<entry key="com.itheima.exception.MyException" # 异常类型value="error"/> # 错误视图
<entry key="java.lang.ClassCastException" value="error"/>
</map>
</property>
</bean>
自定义异常处理
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
//处理异常的代码实现
//创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("exceptionPage");
return modelAndView;
}
}
配置异常处理器并编写异常页面
<bean id="exceptionResolver"
class="com.itheima.exception.MyExceptionResolver"/>