第一节 AspectJ简化AOP配置
- 通过ProxyFactoryBean配置AOP存在的问题
a) 这种配置方式是一个过渡方式, 为了让大家理解AOP就是对动态代理的具体应用.
b) 这种配置方式需要一个切点一个切点的配置, 不灵活, 配置信息越写越多.
c) 使用AspectJ方式进行配置, 可以解决上述问题. - AspectJ方式配置AOP
a) AspectJ是一个插件, 需要额外导包: aspectjweaver.jar
b) 特点是可以支持通配符配置, *作为通配符, 可以代表包, 类, 方法…
c) 只需要配置一次, 后续所有匹配的位置都将变成切点.
d) 需要遵循特定的规范. - AspectJ配置步骤
a) 导包: aspectjweaver.jar
b) 在spring配置文件中引入aop命名空间
c) 通过指定的标签进行aop配置
<?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">
<!--管理service对象-->
<bean id="userService" class="com.bjsxt.service.impl.UserServiceImpl" />
<!--管理通知对象-->
<bean id="txAdvice" class="com.bjsxt.advice.TxAdvice" />
<!--配置aop-->
<aop:config>
<!--配置切点-->
<!--
id: 唯一标识切点
expression: 切点表达式, 让spring通过这个表达式定位切点
execution(), 固定语法
* 任意类型的方法返回值
包名.*.* 指定包下的任意类的任意方法
(..) 任意数量,任意类型的参数列表
-->
<aop:pointcut id="pc" expression="execution(* com.bjsxt.service.*.*(..))"/>
<!--配置通知-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc" />
</aop:config>
</beans>
第二节 POJO实现AOP(了解)
POJO指的是除了jdk之外, 没有其他任何依赖的java类. 之前实现AOP通知, 要求必须实现指定的接口(MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice, MethodInterceptor). POJO实现AOP指的是通知不实现任何接口, 就是一个普通的java类. 好处: 通知类简单; 缺点: 配置复杂, 需要通过指定的标签将POJO模拟成对应的通知.
public class DemoAdvice {
public void myBefore() {
System.out.println("[前置通知]");
}
public void myAfter(String result) {
System.out.println("[后置通知], 返回结果是: " + result);
}
public void myThrows(Exception e) {
System.out.println("[异常通知], 异常信息是: " + e.getMessage());
}
}
<?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">
<!--管理service对象-->
<bean id="demoService" class="com.bjsxt.service.DemoService" />
<!--管理通知对象-->
<bean id="demoAdvice" class="com.bjsxt.advice.DemoAdvice" />
<!--配置AOP-->
<aop:config>
<!--配置切点-->
<aop:pointcut id="pc" expression="execution(* com.bjsxt.service.*.*(..))"/>
<!--配置切面, 将POJO模拟成指定的通知-->
<aop:aspect ref="demoAdvice">
<aop:before method="myBefore" pointcut-ref="pc" />
<aop:after-returning method="myAfter" pointcut-ref="pc" returning="result" />
<aop:after-throwing method="myThrows" pointcut-ref="pc" throwing="e" />
</aop:aspect>
</aop:config>
</beans>
第三节 Spring整合MyBatis
- 如何进行整合
Spring可以将已有技术变得更好用, MyBatis是可以简化JDBC操作. 前提:
a) 导包, 要注意Spring默认没有提供整合MyBatis的包, 由MyBatis提供, 需要将整合包导进来.
b) 责任划分, MyBatis负责数据库的操作(映射文件xxxMapper.xml); Spring负责整体的管理工作(管理数据源, 管理事务, 管理MyBatis, 管理对象, 管理AOP…) - 整合流程
a) 创建项目并导包:
spring相关:
spring-core, spring-beans, spring-context, spring-expression
spring-aop, aopalliance, aspectjweaver
commons-logging
spring-jdbc, spring-tx: 管理数据源和事务
spring-web: 提供web相关的资源(监听器, 过滤器)
MyBatis相关:
mybatis, mysql-connector-java
log4j
整合包
mybatis-spring, 注意版信息(http://mybatis.org/spring/zh/index.html)
其他包
jstl, junit, hamcrest
b) 提供配置(重点)
db.properties – 数据库连接信息
log4j.properties – 日志配置信息
spring相关配置信息(拆分)
applicationContext-service.xml – 负责管理service对象
<?xml version="1.0" encoding="UTF-8"?>
applicationContext-mybatis.xml – 负责管理MyBatis(整合)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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整合MyBatis的配置信息-->
<!--加载资源文件, 需要使用context命名空间-->
<context:property-placeholder location="classpath:config/commons/*.properties" />
<!--配置数据源, 在spring-jdbc.jar中提供了一个测试用的数据源, DriverManagerDataSource-->
<!--常见的数据源: dbcp, c3p0, druid, hikariCP-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</bean>
<!--mybatis-spring.jar, SqlSessionFactoryBean用于构建工厂对象-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--数据源-->
<property name="dataSource" ref="dataSource" />
<!--配置别名-->
<property name="typeAliasesPackage" value="com.bjsxt.pojo" />
</bean>
<!--配置映射扫描, mybatis-spring.jar, MapperScannerConfigurer-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--扫描位置-->
<property name="basePackage" value="com.bjsxt.mapper" />
<!--注入工厂-->
<property name="sqlSessionFactoryBeanName" value="factory" />
</bean>
</beans>
applicationContext-tx.xml – 负责事务管理(见第四节)
MyBatis配置信息
映射文件 – 定义SQL语句
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配置文件的位置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring/*.xml</param-value>
</context-param>
<!--监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--字符编码过滤器, post乱码问题-->
<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>
</web-app>
- 代码编写
a) pojo(略)
b) jsp
<table>
<tr>
<td>编号</td>
<td>用户名</td>
<td>姓名</td>
<td>年龄</td>
<td>生日</td>
<td>注册时间</td>
<td>操作</td>
</tr>
<c:forEach items="${list}" var="u">
<tr>
<td>${u.id}</td>
<td>${u.username}</td>
<td>${u.realname}</td>
<td>${u.age}</td>
<td>${u.birthday}</td>
<td>${u.regTime}</td>
<td>
<a href="delUser?id=${u.id}" onclick="return confirm('确定要删除吗?');">删除</a>
</td>
</tr>
</c:forEach>
</table>
c) servlet
@WebServlet("/userList")
public class UserListServlet extends HttpServlet {
private UserService userService;
@Override
public void init() throws ServletException {
// 从application作用域中拿到spring容器
WebApplicationContext context =
WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
// 为userService赋值
this.userService = context.getBean("userService", UserService.class);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 调用服务层方法获取用户列表集合
List<User> list = this.userService.userList();
// 保存到作用域
req.setAttribute("list", list);
// 页面跳转
req.getRequestDispatcher("/index.jsp").forward(req, resp);
}
}
@WebServlet("/delUser")
public class UserDelServlet extends HttpServlet {
private UserService userService;
@Override
public void init() throws ServletException {
WebApplicationContext context =
WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
this.userService = context.getBean("userService", UserService.class);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 接收参数
int id = Integer.parseInt(req.getParameter("id"));
// 调用服务方法进行删除操作
userService.delUser(id);
// 重新查询用户列表
resp.sendRedirect(req.getContextPath() + "/userList");
}
}
d) service
public class UserServiceImpl implements UserService {
// 需要在spring创建userService对象时将userMapper进行注入
private UserMapper userMapper;
public UserMapper getUserMapper() {
return userMapper;
}
@Override
public void delUser(int id) {
userMapper.delUser(id);
}
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public List<User> userList() {
return userMapper.selAll();
}
}
e) mapper
public interface UserMapper {
List<User> selAll();
void delUser(int id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjsxt.mapper.UserMapper">
<select id="selAll" resultType="user">
select
id, username, password, realname, age, birthday, reg_time regTime
from
tb_user
</select>
<delete id="delUser">
delete from tb_user where id=#{id}
</delete>
</mapper>
第四节 声明式事务
- 什么是声明式事务
事务的管理方式可以分为两种:
a) 编程式事务管理: 通过代码编写的形式进行事务管理的方式
b) 声明式事务管理: 通过配置的形式进行事务管理的方式
Spring中的声明式事务是借助AOP实现的. 声明事务还提供了对事务更细致的控制. - 完成声明式事务的配置
必须依赖spring-tx.jar包和tx及aop命名空间
<?xml version="1.0" encoding="UTF-8"?>
<tx:advice id=“txAdvice” transaction-manager=“txManager”>
tx:attributes
<tx:method name="sel*" read-only="true" />
<tx:method name="query*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="*List" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<!--配置AOP-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.bjsxt.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc" />
</aop:config>
</beans>
- 声明式事务中常见的属性介绍
3.1 name属性
通过通配符定义方法名称, 用于区分查询操作和增删改操作
3.2 read-only属性
布尔值, 默认为false, 表示增删改操作, spring会进行事务管理; 如果设置为true, 表示只读操作(查询), spring不对其进行事务管理, 能够提升查询的效率.
3.3 rollback-for属性
用于指定发生什么异常时要进行事务的回滚. 默认不需要配置, spring会在发生运行时异常时进行事务的回滚.
3.4 no-rollback-for属性
用于指定当发生什么异常时不进行事务的回滚. 基本不会配置.
3.5 isolation属性
用于指定事务的隔离级别. 取值有:
DEFAULT, 表示采用数据库默认的隔离级别
READ_UNCOMMITTED, 表示可以读取到未提交的数据. 可能发生脏读, 不可重复读和幻读. 安全性最低, 效率最高.
READ_COMMITTED, 表示只能读取到已经提交的数据, 可以防止脏读, 无法防止不可重复读和幻读.
REPEATABLE_READ, 表示可以重复读取. 可以防止脏读和不可重复读. 可能发生幻读. 实现方式是给行加锁.
SERIALIZABLE, 表示序列化(排队). 级别最高, 防止发生脏读, 不可重复读和幻读. 给整张表加锁. 安全性最高, 效率最差.
需要先明白几个专业词汇:
脏读: 读取脏数据(未提交的数据)的过程
不可重复读: 在同一次事务中, 两次读取的数据不一致.
幻读: 针对整张表格查询, 同一次事务中, 两次查询整张表格结果不一致
3.6 propagation属性
事务的传播行为. 常用的属性:
REQUIRED, 默认值. 必须在事务环境下执行, 如果当前有事务, 则使用当前事务执行; 如果当前没有事务, 创建事务执行.
REQUIRES_NEW. 必须在事务环境下执行, 如果当前没有事务, 创建事务执行; 如果当前有事务, 将当前事务挂起, 创建新事务执行, 结束后原来的事务继续执行.
SUPPORTS. 支持事务环境. 如果当前有事务, 使用当前事务环境执行; 如果当前没有事务, 可以在非事务环境执行.
NOT_SUPPORTED. 不支持事务. 如果当前没有事务, 则正常执行; 如果当前有事务, 将事务挂起, 在非事务环境下执行, 之后原来的事务继续执行.
MANDATORY. 必须在事务环境执行. 如果当前有事务, 则在当前事务环境执行; 如果当前没有事务, 直接抛出异常.
NEVER. 必须没有事务. 如果当前没有事务, 正常执行; 如果当前有事务, 直接抛出异常.
NESTED. 嵌套的. 如果当前没有事务, 创建事务执行; 如果当前已有事务, 再创建一个嵌套的事务继续执行(不会挂起原有的事务).