Spring完整知识三(完结)
Spring集成MyBatis
- 注意
- Spring注解形式集成MyBatis时,若SQL语句比较复杂则仍采用映射文件形式书写SQL语句;反之则用注解形式书写SQL语句,具体可详见Spring注解形式
环境准备相同步骤
-
Step1: 导入相关坐标,完整pom.xml文件代码如下
- 导入Spring基础坐标:spring-context
- 导入Spring提供的监听器
ContextLoaderListener
的相关坐标:spring-web - 导入Spring集成Web环境相关坐标:servlet、jsp
- 导入Spring注解相关坐标:Annotation
- 导入数据库相关坐标:mysql、数据源坐标(druid、cp30)
- 导入Spring集成MyBatis相关坐标:mybatis、spring-jdbc、mybatis-spring
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>MyBatisDemoTwo</artifactId><packaging>war</packaging><version>1.0-SNAPSHOT</version><name>MyBatisDemoTwo Maven Webapp</name><url>http://maven.apache.org</url><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><!--===================Spring基础坐标=======================--><!--spring坐标--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.1.6</version></dependency><!--===================Spring自带监听器ContextLoaderListener所需坐标=======================--><!--spring-web--><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.25.RELEASE</version></dependency><!--===================Spring集成Web环境相关坐标=======================--><!-- servlet--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><!--jsp--><dependency><groupId>javax.servlet.jsp</groupId><artifactId>javax.servlet.jsp-api</artifactId><version>2.3.3</version><scope>provided</scope></dependency><!--===================Spring注解相关坐标=======================--><!--Annotation坐标--><dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>1.3.2</version></dependency><!--=====================数据库相关坐标=========================--><!--mysql坐标--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><!--druid坐标--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.18</version></dependency><!--c3p0坐标--><dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.5</version></dependency><!--=====================MyBatis相关坐标=========================--><!--MyBatis坐标--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.16</version></dependency><!--mybatis-spring--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>3.0.3</version></dependency><!--spring-jdbc--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.1.10</version></dependency></dependencies><build><finalName>MyBatisDemoTwo</finalName><plugins><!-- Tomcat插件 --><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version></plugin></plugins></build> </project>
-
Step2: 右键源代码配置文件目录(即资源文件
resources
)→New
→File
,创建properties配置文件,博主文件名为jdbc.properties
,该配置文件代码如下- 注意: properties配置文件中配置的各个属性前必须添加个
id.
(即id.属性
,比如:属性url
就设置为id.url
,博主设置的为jdbc.url
),以供Spring配置文件可以使用属性占位符${}
语法引用这些属性
#driverClassName代表数据库驱动,后跟驱动全类名(在MySQL驱动jar包下的META-INF下的services文件夹下的java.sql.Driver文件内) jdbc.driverClassName=com.mysql.cj.jdbc.Driver # 数据库连接URL jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai # 数据库用户名 jdbc.username=root # 数据库密码 jdbc.password=123456 # 初始化连接数量---即容器中初始的数据库连接数量 jdbc.initialSize=5 # 最大活跃连接数量---容器中初始为5个,但若5个用完了,此时可以在申请5个数据库连接数量 #也就是说容器中最多存放10个数据库连接 jdbc.maxActive=10 # 获取连接时的最大等待时间,单位:毫秒。---与数据库进行连接时若超过3s仍未连接成功,则会报错 jdbc.maxWait=3000 #最小空闲连接数量---minIdle=5 # 配置检测连接是否有效的SQL,可以是一个查询语句,如果不指定则默认为"SELECT 1"---validationQuery=SELECT 1 # 是否开启自动提交事务---defaultAutoCommit=true
- 注意: properties配置文件中配置的各个属性前必须添加个
-
Step3: 创建数据库表tb_brand 并使IDEA与数据库建立连接 ,SQL代码如下
DROP TABLE IF EXISTS tb_brand;-- 创建品牌表brand CREATE TABLE IF NOT EXISTS tb_brand (-- id 主键id int PRIMARY KEY auto_increment,-- 品牌名称brand_name VARCHAR(20),-- 企业名称company_name VARCHAR(20),-- 排序字段ordered INT,-- 描述信息description VARCHAR(100),-- 状态:0:禁用 1:启用status INT );-- 添加数据 INSERT INTO tb_brand(brand_name, company_name, ordered, description, status) VALUES ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),('华为', '华为技术有限公司', 100, '华为致力于构建万物互联的世界', 1),('小米', '小米科技有限公司', 50, 'Are you OK', 1);SELECT * FROM tb_brand;
Spring注解形式
项目完整框架图
环境准备
-
Step1: 创建一个与三层架构包同级的pojo包,并在该包下创建实体类
Brand
,代码所如下package at.guigu.pojo;import org.apache.ibatis.type.Alias;@Alias("brand") public class Brand {// id 主键private Integer id;// 品牌名称private String brandName;// 企业名称private String companyName;// 排序字段 用于将某个品牌显示在最前面让消费者看到private Integer ordered;// 描述信息private String description;// 状态:0:禁用 1:启用private Integer status;public Brand() {}public Brand(Integer id, String brandName, String companyName, Integer ordered, String description, Integer status) {this.id = id;this.brandName = brandName;this.companyName = companyName;this.ordered = ordered;this.description = description;this.status = status;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getBrandName() {return brandName;}public void setBrandName(String brandName) {this.brandName = brandName;}public String getCompanyName() {return companyName;}public void setCompanyName(String companyName) {this.companyName = companyName;}public Integer getOrdered() {return ordered;}public void setOrdered(Integer ordered) {this.ordered = ordered;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public Integer getStatus() {return status;}public void setStatus(Integer status) {this.status = status;}@Overridepublic String toString() {return "Brand{" +"id=" + id +", brandName='" + brandName + '\'' +", companyName='" + companyName + '\'' +", ordered=" + ordered +", description='" + description + '\'' +", status=" + status +'}';} }
-
Step2: 创建一个与三层架构包同级的
config
包,并在该包下创建拆分配置文件对应的数据源拆分类DataSourceConfiguration
,代码如下(以Druid为例)package at.guigu.config;import com.alibaba.druid.pool.DruidDataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource;import javax.sql.DataSource;// 分配置文件对应的类不用配置@Configuration以及@ComponentScan注解 // 加载properties配置文件<context:property-placeholder location="classpath:jdbc.properties"/> @PropertySource("classpath:jdbc.properties") public class DataSourceConfiguration {@Value("${jdbc.driverClassName}")private String driverClassName;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;/*** Druid对应的bean* Spring会将当前方法的返回值以指定的id存储到Spring的IOC容器中* @return* @throws Exception*/@Bean("dataSourceDruid")public DataSource getDruidDataSource() throws Exception{// 创建数据源对象DruidDataSource dataSource = new DruidDataSource();// 设置数据源基本连接数据dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;} }
-
Step3: 在config包下创建拆分配置文件MyBatis对应的拆分类
MyBatisConfiguration
,代码如下package at.guigu.config;import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.Bean;import javax.sql.DataSource;public class MyBatisConfiguration {@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();// 相当于设置别名<package name="at.guigu.pojo"/>sqlSessionFactoryBean.setTypeAliasesPackage("at.guigu.pojo");// 相当于配置数据库连接信息sqlSessionFactoryBean.setDataSource(dataSource);return sqlSessionFactoryBean;}// 映射扫描配置类,相当于引入dao包下所有接口对应的SQL映射文件<package name="at.guigu.dao"/>@Beanpublic MapperScannerConfigurer mapperScannerConfigurer() {MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();mapperScannerConfigurer.setBasePackage("at.guigu.dao");return mapperScannerConfigurer;} }
-
Step4: 在config包下创建Spring主配置文件对应的主类
SpringConfiguration
,并引入分配置文件对应的拆分类DataSourceConfiguration
以及MyBatisConfiguration
,代码如下package at.guigu.config;import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import;// 该注解代表该类是Spring的核心配置类 @Configuration // 配置注解的组件扫描<context:component-scan base-package="at.guigu"></context:component-scan> @ComponentScan("at.guigu") @MapperScan("at.guigu.dao") // 引入拆分配置文件<import resource="applicationContext-xxx.xml"/> @Import({DataSourceConfiguration.class, MyBatisConfiguration.class}) public class SpringConfiguration { }
-
Step5: 创建三层架构包,且初始代码分别如下
-
在持久层dao包下创建BrandDao接口
package at.guigu.dao;import org.apache.ibatis.annotations.Mapper;@Mapper public interface BrandDao {}
@Mapper
注解作用: 用于标记单个Mapper接口,让MyBatis生成它的实现类并注入到Spring容器中。也就是说此时不需要创建持久层的实现类,有IOC容器自动创建,其唯一标识id为对应接口名首字母大写(即brandDao
)该注解也可以使用
@MapperScan(at.guigu.dao)
来代替,不过该注解需写在Spring的核心配置文件中,表示: 用于扫描指定包中的所有接口,将它们自动注册为Spring的Bean并作为Mapper以上两个注解均在示例中演示,做项目时可根据实际情况选择
-
在该Maven项目的源代码配置文件目录(即main包下的resources目录下)创建多级目录,然后在与
BrandDao
接口对应的目录下创建 SQL映射文件BrandDao.xml
,如图所示,SQL映射文件代码如下所示<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:名称空间--> <mapper namespace="at.guigu.dao.BrandDao"><!--结果映射--><resultMap id="brandResultMap" type="brand"><!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可--><result column="brand_name" property="brandName"/><result column="company_name" property="companyName"/></resultMap> </mapper>
- 注意:在MyBatis配置类的形式中,
sqlSessionFactoryBean.setTypeAliasesPackage()
方法设置别名无效,目前还未知原因,所以结果映射中的type="brand"
会标红报错,所以解决办法为:利用@Alias("别名")
注解为类的全类名设置类型别名
- 注意:在MyBatis配置类的形式中,
-
在业务层service包下创建BrandService类,初始代码如下
package at.guigu.service;import at.guigu.dao.BrandDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;@Service("brandService") public class BrandService {@Autowiredprivate BrandDao brandDao;}
-
在表现层web包下创建BrandServlet类,初始代码如下
package at.guigu.web;import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException;public class BrandServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
-
-
Step6: 在web项目核心目录(即
WEB-INF
)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener
监听器、web配置。完整代码如下<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--配置Spring配置类的全局初始化参数--><context-param><param-name>contextClass</param-name><param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value></context-param><context-param><!--定义参数的名称,必须是唯一的--><param-name>contextConfigLocation</param-name><!--定义参数的值--><param-value>at.guigu.config.SpringConfiguration</param-value></context-param><!--监听器--><!--配置Spring所提供的`ContextLoaderListener` 监听器--><listener><!--监听器类的全限定名--><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--声明一个Servlet--><servlet><!--声明的Servlet的类名--><servlet-name>BrandServlet</servlet-name><!--声明的Servlet的全限定名--><servlet-class>at.guigu.web.BrandServlet</servlet-class></servlet><!--将URL模式映射到特定的Servlet上(即BrandServlet)--><servlet-mapping><!--指定的Servlet的类名--><servlet-name>BrandServlet</servlet-name><!--给指定的Servlet设置url,相当于@WebServlet("/brandServle")--><url-pattern>/brandServlet</url-pattern></servlet-mapping> </web-app>
查询所有数据
SpringMVC注解开发形式仅以查询所有数据示例,其它增删改操作可根据MyBatis完整知识点汇总示例操作
-
Step1: 在dao包下的
BrandDao
接口中写入查询方法,然后在对应的SQL映射文件中写入对应SQL语句注意:简单查询SQL语句采用注解方式,复杂SQL语句采用映射文件方式
-
BrandDao
接口代码如下package at.guigu.dao;import at.guigu.pojo.Brand; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select;import java.util.List; import java.util.Map;@Mapper public interface BrandDao {// 查询所有条数据@Select("select * from tb_brand")List<Brand> all();// 查询单条数据@Select("select * from tb_brand where id = #{id}")Brand selectById(@Param("id") int id);//静态单条件查询@Select("select * from tb_brand where id > #{id}")List<Brand> selectBySingleConOne(int id);// 动态单条件查询——对象参数接收List<Brand> selectBySingleConTwo(Brand brand);// 动态单条件查询——Map集合参数接收List<Brand> selectBySingleConTwoo(Map map);// 动态多条件查询——对象参数接收List<Brand> selectByMaxConOne(Brand brand);// 动态多条件查询——Map集合参数接收List<Brand> selectByMaxConTwo(Map map); }
注意:在java文件中利用注解形式书写SQL语句时,不需要对特殊字符进行转义
-
SQL映射文件BrandDao.xml文件代码如下
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:名称空间--> <mapper namespace="at.guigu.dao.BrandDao"><!--结果映射--><resultMap id="brandResultMap" type="brand"><!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可--><result column="brand_name" property="brandName"/><result column="company_name" property="companyName"/></resultMap><!--动态单条件查询——对象参数接收--><select id="selectBySingleConTwo" resultMap="brandResultMap">select * from tb_brand<where><choose> <!--类似于switch--><when test="status != null">status = #{status}</when><when test="companyName != null and companyName != ''">company_name like #{companyName}</when><when test="brandName != null and brandName != ''">brand_name like #{brandName}</when></choose></where></select><!--动态单条件查询——Map集合参数接收--><select id="selectBySingleConTwoo" resultMap="brandResultMap">select * from tb_brand<where><choose> <!--类似于switch--><when test="status != null">status = #{status}</when><when test="companyName != null and companyName != ''">company_name like #{companyName}</when><when test="brandName != null and brandName != ''">brand_name like #{brandName}</when></choose></where></select><!--动态多条件查询——对象参数接收--><select id="selectByMaxConOne" resultMap="brandResultMap">select * from tb_brand<where><if test="status != null">and status = #{status}</if><if test="companyName != null and companyName != ''">and company_name like #{companyName}</if><if test="brandName != null and brandName != ''">and brand_name like #{brandName}</if></where></select><!--动态多条件查询——Map集合参数接收--><select id="selectByMaxConTwo" resultMap="brandResultMap">select * from tb_brand<where><if test="status != null">and status = #{status}</if><if test="companyName != null and companyName != ''">and company_name like #{companyName}</if><if test="brandName != null and brandName != ''">and brand_name like #{brandName}</if></where></select></mapper>
-
-
Step2: 在业务层service包下的
BrandService
类来调用dao
包下的BrandDao
接口中的方法,代码如下package at.guigu.service;import at.guigu.dao.BrandDao; import at.guigu.pojo.Brand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;import java.util.List; import java.util.Map;@Service("brandService") public class BrandService {@Autowiredprivate BrandDao brandDao;// 查询所有条数据public List<Brand> getAll() {return brandDao.all();}// 查询单条数据public Brand getById(int id) {return brandDao.selectById(id);}// 静态单条件查询public List<Brand> selectBySingleConOne(int id) {return brandDao.selectBySingleConOne(id);}// 动态单条件查询——对象参数接收public List<Brand> selectBySingleConTwo(Brand brand) {return brandDao.selectBySingleConTwo(brand);}// 动态单条件查询——Map集合参数接收public List<Brand> selectBySingleConTwoo(Map map) {return brandDao.selectBySingleConTwoo(map);}// 动态多条件查询——对象参数接收public List<Brand> selectByMaxConOne(Brand brand) {return brandDao.selectByMaxConOne(brand);}// 动态多条件查询——Map集合参数接收public List<Brand> selectByMaxConTwo(Map map){return brandDao.selectByMaxConTwo(map);}}
-
Step3: 在表现层web包下的BrandServlet类中来调用业务层service包中的BrandService类中的方法,代码如下:
package at.guigu.web;import at.guigu.pojo.Brand; import at.guigu.service.BrandService; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException; import java.util.List;public class BrandServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1 获取最大域ServletContext对象ServletContext servletContext = request.getServletContext();//等同于ServletContext servletContext = request.getSession().getServletContext();// 2 获取应用上下文对象(即IOC容器)ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);//3 获取beanBrandService brandService = app.getBean(BrandService.class);//等同于BookServicel bookService = (BookService) app.getBean("bookService");//4 调用方法执行SQL语句List<Brand> brands = brandService.getAll();for (Brand brand : brands) {System.out.println(brand);}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
Tomcat运行截图如下
-
注意:由于此时已经配置了映射扫描的类,所以此时MyBatis会自动扫描
at.guigu.dao
包下的所有接口,并为它们生成相应的代理对象,而不需要使用@Mapper
注解或@MapperScan
注解,此处添加上是为了知道有两个注解可以用来自动生成持久层接口对应的bean
Spring配置文件形式
项目完整框架图
环境准备
-
Step1: 创建一个与三层架构包同级的pojo包,并在该包下创建实体类
Brand
,代码所如下package at.guigu.pojo;public class Brand {// id 主键private Integer id;// 品牌名称private String brandName;// 企业名称private String companyName;// 排序字段 用于将某个品牌显示在最前面让消费者看到private Integer ordered;// 描述信息private String description;// 状态:0:禁用 1:启用private Integer status;public Brand() {}public Brand(Integer id, String brandName, String companyName, Integer ordered, String description, Integer status) {this.id = id;this.brandName = brandName;this.companyName = companyName;this.ordered = ordered;this.description = description;this.status = status;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getBrandName() {return brandName;}public void setBrandName(String brandName) {this.brandName = brandName;}public String getCompanyName() {return companyName;}public void setCompanyName(String companyName) {this.companyName = companyName;}public Integer getOrdered() {return ordered;}public void setOrdered(Integer ordered) {this.ordered = ordered;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public Integer getStatus() {return status;}public void setStatus(Integer status) {this.status = status;}@Overridepublic String toString() {return "Brand{" +"id=" + id +", brandName='" + brandName + '\'' +", companyName='" + companyName + '\'' +", ordered=" + ordered +", description='" + description + '\'' +", status=" + status +'}';} }
-
Step2: 右键源代码配置文件目录(即资源文件
resources
)→New
→File
,创建MyBatis核心配置文件(名为mybatis-config.xml
),代码如下:<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><!--设置别名--><typeAliases><package name="at.guigu.pojo"/></typeAliases></configuration>
-
Step3: 右键源代码配置文件目录(即资源文件
resources
)→New
→XML Configuration File
→Spring Config
,创建Spring核心配置文件(名为applicationContext.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"></beans>
- Step3-1: 使用
context
命名空间加载jdbc.properties
文件(前提:需引入context
命名空间和约束路径)context
命名空间:xmlns:context="http://www.springframework.org/schema/context"
context
约束路径:http://www.springframework.org/schema/context
、http://www.springframework.org/schema/context/spring-context.xsd
- Step3-2: 配置数据源对应的bean
- Step3-3: 配置MyBatis的SqlSessionFactory
- 配置数据源
- 配置MyBatis核心配置文件
- 配置别名
- 由于通过Spring的核心配置文件配置别名无效,所以必须在MyBatis核心配置文件中设置别名,然后在Spring的核心配置文件中配置MyBatis核心配置文件
- Step3-4: 引入dao包下所有接口对应的SQL映射文件
- 此时Spring会进行持久层扫描,自动生成该层中对应接口的bean
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--使用`context`命名空间加载 `properties` 文件--><context:property-placeholder location="classpath:jdbc.properties"/><!--Druid对应的bean--><bean id="dataSourceDruid" class="com.alibaba.druid.pool.DruidDataSource"><!--使用属性占位符`${}`语法引用properties文件中的属性--><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!--配置MyBatis的SqlSessionFactory--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--配置数据源--><property name="dataSource" ref="dataSourceDruid"/><!--加载MyBatis的核心配置文件--><property name="configLocation" value="mybatis-config.xml"/><!--配置别名--><property name="typeAliasesPackage" value="at.guigu.pojo"/></bean><!--引入dao包下所有接口对应的SQL映射文件即MyBatis 持久层扫描,会自动生成该层中对应接口的bean--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="at.guigu.dao"/></bean></beans>
- Step3-1: 使用
-
Step4: 创建三层架构包,且初始代码分别如下
-
在持久层dao包下创建BrandDao接口
package at.guigu.dao;public interface BrandDao { }
-
在该Maven项目的源代码配置文件目录(即main包下的resources目录下)创建多级目录,然后在与
BrandDao
接口对应的目录下创建 SQL映射文件BrandDao.xml
,如图所示,SQL映射文件代码如下所示<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:名称空间--> <mapper namespace="at.guigu.dao.BrandDao"><!--结果映射--><resultMap id="brandResultMap" type="brand"><!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可--><result column="brand_name" property="brandName"/><result column="company_name" property="companyName"/></resultMap> </mapper>
-
在业务层service包下创建BrandService类,初始代码如下
package at.guigu.service;import at.guigu.dao.BrandDao;public class BrandService {private BrandDao brandDao;public void setBrandDao(BrandDao brandDao) {this.brandDao = brandDao;}}
注意:此处使用的是setter方法注入,所以必须在业务层中用setter方法将IOC容器中自动生成的持久层接口对应的bean注入到业务层对应的类中
-
在表现层web包下创建BrandServlet类,初始代码如下
package at.guigu.web;import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException;public class BrandServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
-
-
Step5: 在web项目核心目录(即
WEB-INF
)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener
监听器、web配置。完整代码如下<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--配置Spring配置类的全局初始化参数--><context-param><param-name>contextClass</param-name><param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value></context-param><context-param><!--定义参数的名称,必须是唯一的--><param-name>contextConfigLocation</param-name><!--定义参数的值--><param-value>at.guigu.config.SpringConfiguration</param-value></context-param><!--监听器--><!--配置Spring所提供的`ContextLoaderListener` 监听器--><listener><!--监听器类的全限定名--><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--声明一个Servlet--><servlet><!--声明的Servlet的类名--><servlet-name>BrandServlet</servlet-name><!--声明的Servlet的全限定名--><servlet-class>at.guigu.web.BrandServlet</servlet-class></servlet><!--将URL模式映射到特定的Servlet上(即BrandServlet)--><servlet-mapping><!--指定的Servlet的类名--><servlet-name>BrandServlet</servlet-name><!--给指定的Servlet设置url,相当于@WebServlet("/brandServlet")--><url-pattern>/brandServlet</url-pattern></servlet-mapping> </web-app>
查询所有数据
SpringMVC注解开发形式仅以查询所有数据示例,其它增删改操作可根据MyBatis完整知识点汇总示例操作
-
Step1: 在dao包下的
BrandDao
接口中写入查询方法,然后在对应的SQL映射文件中写入对应SQL语句-
BrandDao
接口代码如下package at.guigu.dao;import at.guigu.pojo.Brand;import java.util.List; import java.util.Map;public interface BrandDao {// 查询所有条数据List<Brand> all();// 查询单条数据:通过id查询Brand selectById(int id);// 静态单条件查询List<Brand> selectBySingleConOne(int id);// 动态单条件查询——对象参数接收List<Brand> selectBySingleConTwo(Brand brand);// 动态单条件查询——Map集合参数接收List<Brand> selectBySingleConTwoo(Map map);// 动态多条件查询——对象参数接收List<Brand> selectByMaxConOne(Brand brand);// 动态多条件查询——Map集合参数接收List<Brand> selectByMaxConTwo(Map map); }
-
SQL映射文件BrandDao.xml文件代码如下
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:名称空间--> <mapper namespace="at.guigu.dao.BrandDao"><!--结果映射--><resultMap id="brandResultMap" type="brand"><!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可--><result column="brand_name" property="brandName"/><result column="company_name" property="companyName"/></resultMap><!--查询所有条数据resultMap属性值为结果映射的id属性值--><select id="all" resultMap="brandResultMap">select * from tb_brand;</select><!--查询单条数据:通过id查询parameterType可设置传入的参数类型 ,它可以省略不写--><select id="selectById" parameterType="int" resultMap="brandResultMap">select * from tb_brand where id = #{id};</select><!--静态单条件查询--><select id="selectBySingleConOne" parameterType="int" resultMap="brandResultMap"># 此处根据情况选择使用转义字符还是CDATA区select * from tb_brand where id <![CDATA[>]]>#{id};</select><!--动态单条件查询——对象参数接收--><select id="selectBySingleConTwo" resultMap="brandResultMap">select * from tb_brand<where><choose> <!--类似于switch--><when test="status != null">status = #{status}</when><when test="companyName != null and companyName != ''">company_name like #{companyName}</when><when test="brandName != null and brandName != ''">brand_name like #{brandName}</when></choose></where></select><!--动态单条件查询——Map集合参数接收--><select id="selectBySingleConTwoo" resultMap="brandResultMap">select * from tb_brand<where><choose> <!--类似于switch--><when test="status != null">status = #{status}</when><when test="companyName != null and companyName != ''">company_name like #{companyName}</when><when test="brandName != null and brandName != ''">brand_name like #{brandName}</when></choose></where></select><!--动态多条件查询——对象参数接收--><select id="selectByMaxConOne" resultMap="brandResultMap">select * from tb_brand<where><if test="status != null">and status = #{status}</if><if test="companyName != null and companyName != ''">and company_name like #{companyName}</if><if test="brandName != null and brandName != ''">and brand_name like #{brandName}</if></where></select><!--动态多条件查询——Map集合参数接收--><select id="selectByMaxConTwo" resultMap="brandResultMap">select * from tb_brand<where><if test="status != null">and status = #{status}</if><if test="companyName != null and companyName != ''">and company_name like #{companyName}</if><if test="brandName != null and brandName != ''">and brand_name like #{brandName}</if></where></select> </mapper>
-
-
Step2: 在业务层service包下的
BrandService
类来调用dao
包下的BrandDao
接口中的方法,代码如下package at.guigu.service;import at.guigu.dao.BrandDao; import at.guigu.pojo.Brand;import java.util.List; import java.util.Map;public class BrandService {private BrandDao brandDao;public void setBrandDao(BrandDao brandDao) {this.brandDao = brandDao;}// 查询所有条数据public List<Brand> getAll() {return brandDao.all();}// 查询单条数据public Brand getById(int id) {return brandDao.selectById(id);}// 静态单条件查询public List<Brand> selectBySingleConOne(int id) {return brandDao.selectBySingleConOne(id);}// 动态单条件查询——对象参数接收public List<Brand> selectBySingleConTwo(Brand brand) {return brandDao.selectBySingleConTwo(brand);}// 动态单条件查询——Map集合参数接收public List<Brand> selectBySingleConTwoo(Map map) {return brandDao.selectBySingleConTwoo(map);}// 动态多条件查询——对象参数接收public List<Brand> selectByMaxConOne(Brand brand) {return brandDao.selectByMaxConOne(brand);}// 动态多条件查询——Map集合参数接收public List<Brand> selectByMaxConTwo(Map map){return brandDao.selectByMaxConTwo(map);} }
-
Step3: 在Spring的核心配置文件中配置业务层对应的bean,并将持久层对应的bean依赖注入到该业务层中,代码如下
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--使用`context`命名空间加载 `properties` 文件--><context:property-placeholder location="classpath:jdbc.properties"/><!--Druid对应的bean--><bean id="dataSourceDruid" class="com.alibaba.druid.pool.DruidDataSource"><!--使用属性占位符`${}`语法引用properties文件中的属性--><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!--配置MyBatis的SqlSessionFactory--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--配置数据源--><property name="dataSource" ref="dataSourceDruid"/><!--加载MyBatis的核心配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"/><!--配置别名--><property name="typeAliasesPackage" value="at.guigu.pojo"/></bean><!--引入dao包下所有接口对应的SQL映射文件即MyBatis 持久层扫描,会自动生成该层中对应接口的benan--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="at.guigu.dao"/></bean><bean id="brandService" class="at.guigu.service.BrandService"><!--在对应的业务层中必须有setter方法,否则name属性值会报错--><property name="brandDao" ref="brandDao"/></bean></beans>
-
Step4: 在表现层web包下的BrandServlet类中来调用业务层service包中的BrandService类中的方法,代码如下:
package at.guigu.web;import at.guigu.pojo.Brand; import at.guigu.service.BrandService; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException; import java.util.List;public class BrandServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1 获取最大域ServletContext对象ServletContext servletContext = request.getServletContext();//等同于ServletContext servletContext = request.getSession().getServletContext();// 2 获取应用上下文对象(即IOC容器)ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);//3 获取beanBrandService brandService = app.getBean(BrandService.class);//等同于BookServicel bookService = (BookService) app.getBean("bookService");//4 调用方法执行SQL语句List<Brand> brands = brandService.getAll();for (Brand brand : brands) {System.out.println(brand);}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
Tomcat运行截图如下
AOP
-
AOP(Aspect-Oriented Programming,面向切面编程),是一种编程范式,它在Spring中可用于将那些与业务无关,但会对多个对象产生影响的公共行为和逻辑,抽取成通知复用,来降低耦合,提高系统的可维护性
- 作用:在程序运行期间,可以在不修改源码的情况下对业务方法进行功能增强
- 优势:减少重复代码,降低耦合,提高开发效率,并且便于维护
-
底层原理
- AOP底层是通过Spring提供的动态代理技术实现的。在运行期间,Spring框架会动态的监控切入点方法的执行,一旦监控到切入点方法被运行,它就会通过动态代理技术动态的生成切入点方法所在的目标对象的代理对象。然后它会根据通知类别,在代理对象的对应位置处将对应通知织入,从而完成完整的代码逻辑运行
-
AOP动态代理技术
-
JDK代理:它是基于接口的动态代理技术
- 也就是说业务层必须有接口以及接口对应的实现类
- 此时代理对象会实现目标接口
-
cglib代理:它是基于父类的动态代理技术
- 也就是说业务层无接口,只有业务层类
- 此时代理对象为业务层目标类的子类
-
注意:Spring框架在底层会根据目标类是否实现了接口来决定采用哪种动态代理方式
-
-
AOP涉及几个概念
-
目标对象(Target): 原始功能去掉共性功能后所对应的类所产生的对象,这种对象是无法直接完成最终工作的
-
代理(Proxy): 目标对象无法直接完成工作,需要对其进行功能回填,这就需要原始对象的代理对象实现
- SpringAOP的 核心本质是采用代理模式实现的
-
连接点(JoinPoint): 是程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
- 在SpringA0P中,指的是方法。(即在Spring中可以被增强的方法叫做连接点)
-
切入点(Pointcut): 匹配连接点的式子(也就是指我们要对哪些连接点进行拦截的定义)
- 在SpringAoP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法,比如:
- 一个具体方法:com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
- 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
- 在SpringAoP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法,比如:
-
通知/增强(Advice): 指拦截到连接点之后要做的事情就是通知
- 在切入点处执行的操作,也就是共性功能
- 在SpringAoP中,功能最终以方法的形式呈现
-
通知类: 定义通知的类
-
切面(Aspect): 描述通知与切入点的对应关系,可以理解成通知与切入点的结合
-
织入(Weaving): 指把通知/增强应用到目标对象来创建新的代理对象的过程。
- Spring采用动态代理织入;AspectJ采用编译器织入和类装载期织入
-
快速入门
要求:在接口执行前输出当前系统时间
注意:本示例为简单基础示例,所以不在创建持久层dao包
环境准备
-
Step1: 导入坐标
-
导入Spring基础坐标:spring-context
-
导入Spring提供的监听器
ContextLoaderListener
的相关坐标:spring-web -
导入Spring集成Web环境相关坐标:servlet、jsp
-
导入Spring注解相关坐标:Annotation
-
导入与AOP相关的坐标:aop、aspectj
-
AOP坐标会在导入spring-context坐标后系统自动导入,如图所示
-
-
导入数据库相关坐标:mysql、数据源坐标(druid、cp30)
-
导入Spring集成JUnit相关坐标:junit、spring-test
-
导入Spring集成MyBatis相关坐标:mybatis、spring-jdbc、mybatis-spring
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>SpringAOPDemo</artifactId><packaging>war</packaging><version>1.0-SNAPSHOT</version><name>SpringAOPDemo Maven Webapp</name><url>http://maven.apache.org</url><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><!--===================Spring基础坐标=======================--><!--spring坐标--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.1.6</version></dependency><!--===================Spring自带监听器ContextLoaderListener所需坐标=======================--><!--spring-web--><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.25.RELEASE</version></dependency><!--===================Spring集成Web环境相关坐标=======================--><!-- servlet--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><!--jsp--><dependency><groupId>javax.servlet.jsp</groupId><artifactId>javax.servlet.jsp-api</artifactId><version>2.3.3</version><scope>provided</scope></dependency><!--===================Spring注解相关坐标=======================--><!--Annotation坐标--><dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>1.3.2</version></dependency><!--=====================AOP相关坐标=========================--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.22.1</version></dependency><!--=====================数据库相关坐标=========================--><!--mysql坐标--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><!--druid坐标--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.18</version></dependency><!--c3p0坐标--><dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.5</version></dependency><!--===================Spring集成junit相关坐标=======================--><!--junit坐标--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><!--spring-test坐标--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>6.1.6</version><scope>test</scope></dependency><!--=====================MyBatis相关坐标=========================--><!--MyBatis坐标--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.16</version></dependency><!--mybatis-spring--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>3.0.3</version></dependency><!--spring-jdbc--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.1.10</version></dependency></dependencies><build><finalName>SpringAOPDemo</finalName><plugins><!-- Tomcat插件 --><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version></plugin></plugins></build> </project>
-
基于XML的AOP开发
-
步骤
-
导入相关坐标(略)
-
创建目标接口和目标类
-
创建通知类(即切面类,内含通知/增强方法)
-
将目标类与通知类的对象创建权交给Spring
- 在Spring核心配置文件中配入织入关系
-
代码测试
-
-
Step1: 创建三层架构包中的业务层service包及相关接口和实现类,代码如下
-
业务层接口及其实现类
package at.guigu.service; public interface BrandService {void save();void update(); }
package at.guigu.service.impl; import at.guigu.service.BrandService;public class BrandServiceImpl implements BrandService {@Overridepublic void save() {System.out.println("bookDao save...");}@Overridepublic void update() {System.out.println("bookDao update...");} }
-
-
Step2: 创建一个与三层架构包同级的aop包,并在该包下创建MyAdvice通知(即切面)类,代码如下
package at.guigu.aop;public class MyAdvice {public void method1() {System.out.println(System.currentTimeMillis());} }
-
Step3: 右键源代码配置文件目录(即资源文件
resources
)→New
→XML Configuration File
→Spring Config
,文件名为applicationContext.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"></beans>
- Step3-1: 在Spring的核心配置文件中引入AOP的命名空间:
xmlns:aop="http://www.springframework.org/schema/aop"
- Step3-2: 在Spring的核心配置文件中引入AOP的约束路径:
http://www.springframework.org/schema/aop
、http://www.springframework.org/schema/aop/spring-aop.xsd
- Step3-3: 配置业务层bean(该示例并未创建持久层,所以不用配置持久层bean)
- Step3-4: 配置切面类(即通知类)对应的bean
- Step3-5: 配置织入关系:将对应通知方法(即增强方法)应用到对应目标对象的对应原始方法上
- 配置普通增强使用
<aop:aspect>
标签 - 配置事务的增强使用
<aop:advisor>
标签,可详见事务控制部分内容
- 配置普通增强使用
- Step3-1: 在Spring的核心配置文件中引入AOP的命名空间:
<!--配置BrandService实现类bean 即目标对象-->
<bean id="brandService" class="at.guigu.service.impl.BrandServiceImpl"/><!--配置通知类(即切面类)对应的bean,即切面对象-->
<bean id="myAdvice" class="at.guigu.aop.MyAdvice"/><!--配置织入关系:即将对应通知方法(即增强方法)应用到对应目标对象的对应原始方法上-->
<aop:config><!--将指定的bean配置为切面类(即通知类),ref属性值为要指定的切面类(即通知类)对应bean的id值--><aop:aspect ref="myAdvice"><!--配置指定切面:切点+通知--><aop:before method="method1" pointcut="execution(void at.guigu.service.BrandService.update())"></aop:before></aop:aspect>
</aop:config>
```
-
Step4: 创建表现层web包,并在该包下创建BrandServlet类,代码如下
package at.guigu.web;import at.guigu.service.BrandService; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;public class BrandServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1 获取最大域ServletContext对象ServletContext servletContext = request.getServletContext();//等同于ServletContext servletContext = request.getSession().getServletContext();// 2 获取应用上下文对象(即IOC容器)ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);//3 获取beanBrandService brandService = app.getBean(BrandService.class);//4 执行要增强的原始方法(即目标方法)brandService.update();}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
注意:由于此时获取的brean是代理对象,所以无法通过
BrandServiceImpl branServiceImpl = app.getBean(BrandServiceImpl.class);
或BrandServiceImpl brandServiceImpl = (BrandServiceImpl) app.getBean("brandServiceImpl");
来获取对应的bean。此时参数只能是接口的类对象。 -
Step5: 在web项目核心目录(即
WEB-INF
)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener
监听器、web配置。完整代码如下<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--全局初始化参数--><context-param><!--定义参数的名称,必须是唯一的--><param-name>contextConfigLocation</param-name><!--定义参数的值--><param-value>classpath:applicationContext.xml</param-value></context-param><!--监听器--><!--配置Spring所提供的`ContextLoaderListener` 监听器--><listener><!--监听器类的全限定名--><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--声明一个Servlet--><servlet><!--声明的Servlet的类名--><servlet-name>BrandServlet</servlet-name><!--声明的Servlet的全限定名--><servlet-class>at.guigu.web.BrandServlet</servlet-class></servlet><!--将URL模式映射到特定的Servlet上(即BrandServlet)--><servlet-mapping><!--指定的Servlet的类名--><servlet-name>BrandServlet</servlet-name><!--给指定的Servlet设置url,相当于@WebServlet("/brandServlet")--><url-pattern>/brandServlet</url-pattern></servlet-mapping> </web-app>
运行后截图如下
基于注解的AOP开发
-
步骤
-
导入相关坐标(略)
-
创建目标接口和目标类
-
创建通知类(即切面类,内含通知/增强方法)
-
将目标类与通知类的对象创建权交给Spring
- 在通知类(即切面类中用注解配置织入关系)
-
在Spring的核心配置类中开启组件扫描和AOP自动代理
-
代码测试
-
-
Step1: 创建三层架构包中的业务层service包及相关接口和实现类,代码如下
-
业务层接口及其实现类
package at.guigu.service;public interface BrandService {void save();void update(); }
package at.guigu.service.impl;import at.guigu.service.BrandService; import org.springframework.stereotype.Service;@Service("bransServiceImpl") public class BrandServiceImpl implements BrandService {@Overridepublic void save() {System.out.println(System.currentTimeMillis());System.out.println("bookDao save...");}@Overridepublic void update() {System.out.println("bookDao update...");} }
-
-
Step2: 创建一个与三层架构包同级的aop包,并在该包下创建MyAdvice通知(即切面类),代码如下
-
Step2-1: 在该类中创建一个通知(即方法),并写入作为公共功能的内容
-
Step2-2: 在该类中用
@Pointcut
定义切入点- 切入点的定义依托一个不具有实际意义的方法(即无参数、无返回值,方法体内无实际逻辑)
-
Step2-3: 绑定切入点与通知的关系,并指定通知的执行位置。(即定义切面)
-
Step2-4: 为通知类添加
@Component
以及@Aspect
注解@Component
:将切面类作为Spring容器中的一个Bean来管理@Aspect
:标记该类为切面类
package at.guigu.aop;import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component;@Aspect @Component public class MyAdvice {// 利用通知注解配置切面:切点+通知// 前置利用通知注解@Before("execution(void at.guigu.service.BrandService.update())")public void method1() {System.out.println(System.currentTimeMillis());} }
-
-
Step3: 创建一个与三层架构包同级的config包,并在该包下创建Spring的核心配置类
SpringConfiguration
,代码如下@EnableAspectJAutoProxy
:启用Spring基于AspectJ 注解驱动的AOP功能package at.guigu.config;import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration @ComponentScan("at.guigu") @EnableAspectJAutoProxy public class SpringConfiguration { }
-
Step4: 创建表现层web包,并在该包下创建BrandServlet类,代码如下
package at.guigu.web;import at.guigu.service.BrandService; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;public class BrandServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1 获取最大域ServletContext对象ServletContext servletContext = request.getServletContext();//等同于ServletContext servletContext = request.getSession().getServletContext();// 2 获取应用上下文对象(即IOC容器)ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);//3 获取beanBrandService brandService = app.getBean(BrandService.class);//4 执行要增强的原始方法(即目标方法)brandService.update();}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
注意:由于此时获取的brean是代理对象,所以无法通过
BrandServiceImpl branServiceImpl = app.getBean(BrandServiceImpl.class);
或BrandServiceImpl brandServiceImpl = (BrandServiceImpl) app.getBean("brandServiceImpl");
来获取对应的bean。此时参数只能是接口的类对象。 -
Step5: 在web项目核心目录(即
WEB-INF
)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener
监听器、web配置。完整代码如下<!--配置Spring配置类的全局初始化参数--><context-param><param-name>contextClass</param-name><param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value></context-param><context-param><!--定义参数的名称,必须是唯一的--><param-name>contextConfigLocation</param-name><!--定义参数的值--><param-value>at.guigu.config.SpringConfiguration</param-value></context-param><!--监听器--><!--配置Spring所提供的`ContextLoaderListener` 监听器--><listener><!--监听器类的全限定名--><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--声明一个Servlet--><servlet><!--声明的Servlet的类名--><servlet-name>BrandServlet</servlet-name><!--声明的Servlet的全限定名--><servlet-class>at.guigu.web.BrandServlet</servlet-class></servlet><!--将URL模式映射到特定的Servlet上(即BrandServlet)--><servlet-mapping><!--指定的Servlet的类名--><servlet-name>BrandServlet</servlet-name><!--给指定的Servlet设置url,相当于@WebServlet("/brandServlet")--><url-pattern>/brandServlet</url-pattern></servlet-mapping>
运行后截图如下
两种开发方式用到的标签及注解
基于XML的AOP开发
标签 | 解释 |
---|---|
<aop:config> | Spring启动AOP配置 |
<aop:config> 的内嵌标签 | 解释 |
<aop:aspect ref> | 将指定的bean配置为切面类(即通知类),ref属性值为要指定的切面类(即通知类)对应bean的id值 |
<aop:aspect ref> 的内嵌标签 | 解释 |
<aop:pointcut id expression/> | 配置切入点,主要用于切入点表达式的抽取。id 为切入点的唯一标识符;expression 为指定切入点的表达式 |
<aop:通知类型 method pointcut-ref></aop:before> | 配置指定切面。aop的: 后为通知类型;method 属性值为对应切面类(即通知类)中的通知方法名;pointcut-ref 属性值为切入点的唯一标识 |
<aop:通知类型 method pointcut></aop:通知类型> | 配置指定切面。aop的: 后为通知类型;method 属性值为对应切面类(即通知类)中的通知方法名;pointcut 属性值为切入点表达式 |
- 注意
- 在不进行切入点表达式抽取时不需要使用
<aop:pointcut id expression/>
以及<aop:通知类型 method pointcut-ref></aop:before>
这两个标签,反之则用这两个标签代替<aop:通知类型 method pointcut></aop:通知类型>
标签 - 切入点表达式抽取可详见切入点表达式部分内容
- 在不进行切入点表达式抽取时不需要使用
基于注解的AOP开发
与AOP相关的注解 | 解释 |
---|---|
@Component | 将对应类作为Spring容器中的一个Bean来管理 |
@Aspect | 标记切面类 |
@EnableAspectJAutoProxy | 启用Spring基于AspectJ 注解驱动的AOP功能 |
@通知类型("切点表达式") | 详见AOP通知类型部分内容 |
工作流程
-
AOP整个流程都是在动态代理模式下进行的
-
步骤如下
-
Spring IOC容器启动
-
读取所有切面 配置中 的切入点
- 即不读取那些未与通知建立关系的切入点,只读取已经建立关系的切入点
-
初始化bean,判定bean对应类中的方法是否匹配到任意切入点
- 若匹配失败则创建对象
- 若匹配成功,则会创建原始对象(即目标对象)的代理对象
-
获取bean执行方法
- 获取bean,调用方法并执行,完整操作
- 由于此时获取的bean是代理对象,所以它会根据代理对象的运行模式运行原始方法(即目标方法)与增强的内容,从而完成操作
-
切入点表达式
-
切入点表法式在基于XML的AOP开发与基于注解的AOP开发的表达式分别如下
pointcut="execution(void at.guigu.service.BrandService.update())"
@Pointcut("execution(void at.guigu.service.BrandService.update())")
- 其中
execution(void at.guigu.service.BrandService.update())
这一部分即为切入点表达式
-
切入点表达式标准格式顺序:
动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
,其中:- 动作关键字:描述切入点的行为动作,比如:execution表示执行到指定切入点
- 访问修饰符:public、private等,可省略
- 异常名:方法定义中抛出指定异常,可省略
-
在实际应用中,若严格按照切点表达式的格式顺序书写则比较麻烦,所以可使用通配符进行快速描述
通配符 解释 *
可用于匹配单个路径段或方法的名称、参数类型等。它可以独立出现,也可以作为前缀或后缀的匹配符出现 ..
可用于匹配包路径中的任意层级或方法中任意数量的参数。它可以独立出现,常用于简化包名与参数的书写。当其作为方法的形式参数出现时,代表该方法的参数可以为0,也可以是任意数量,比如: findId(..)
+
可用于匹配指定类及其所有子类或实现类 execution(public * at.guigu.*.BrandService.find*(*))
:匹配at.guigu包下的任意包中的BrandService类或接口中所有 以find开头的带有一个参数的方法- 返回值为任意返回值,且方法必须有一个参数
execution(public Brand at..BrandService.findById(..))
:匹配at包下及其任意子包中的BrandService类或接口中所有名称为findById且返回值为Brand的方法- 注意:方法参数可有可无,参数数量也可不固定
execution(* *..*Service+.*(..))
:匹配任意返回值、任意包及其子包下的以Service结尾的类或接口的任意子类或实现类的包含任意参数的任意方法- 该表达式代表匹配业务层的所有方法
execution(* at.guigu.*.*Service.save(..))
:匹配业务层下的所有save方法
-
切入点表达式有两种描述方式
- 执行at.guigu.service包下的BrandService接口中的无参数
update
方法:execution(void at.guigu.service.BrandService.update())
,可写为(但不等同于)execution(void at.*.*.*.update())
:匹配返回值为void,at包下的任意子包下的任意包下的任意类/接口的update方法execution(void *..update())
:匹配返回值为void,任意包及其子包下的的任意update方法execution(* *..u*(..))
:匹配任意返回值,任意包及其子包下的以u开头的包含任意数量参数的任意方法execution(* *..*e(..))
:匹配任意返回值,任意包及其子包下的任意以e结尾的包含任意数量参数的任意方法
- 执行at.guigu.service.impl包下的BrandServiceImpl类中的无参数
update
方法:execution(void at.guigu.service.impl.BrandServiceImpl.update())
,可写为(但不等同于)execution(void at.*.*.*.*.update())
:匹配返回值为void,at包下的任意子包下的任意子包下的任意包下的任意类/接口的update方法execution(void at..update())
:匹配返回值为void,at包及其子包下的任意update方法execution(* *..u*(..))
:匹配任意返回值,任意包及其子包下的以u开头的包含任意数量参数的任意方法execution(* *..*e(..))
:匹配任意返回值,任意包及其子包下的任意以e结尾的包含任意数量参数的任意方法
- 执行at.guigu.service包下的BrandService接口中的无参数
-
书写技巧(必须按标准规范开发,否则书写技巧失效)
- 描述切入点通常描述接口,而不描述实现类
- 访问控制修饰符针对接口开发均采用public描述,此时可省略访问控制修饰符描述
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
- 包名书写尽量不使用
..
匹配,效率过低,常用*
做单个包描述匹配,或精准匹配 - 接口名/类名书写名称与模块相关的采用
*
匹配,例如UserService
书写成*Service
,绑定业务层接口名 - 方法名书写以动词进行精准匹配,名词采用
*
匹配,例如getByld
书写成getBy*
,selectAll
书写成selectAll
- 参数规则较为复杂,根据业务方法灵活调整
- 通常不使用异常作为匹配规则
切入点表达式的抽取
-
当多个通知(即增强)的切入点表达式相同时,可以将切点表达式抽取,以此来降低耦合度
-
基于XML的AOP开发的抽取方式
-
在通知(即增强)中使用pointcut-ref属性来代替pointcut属性来引用抽取后的切入点表达式。代码如下
<!--配置织入关系:即将对应通知方法(即增强方法)应用到对应目标对象的对应原始方法上--> <aop:config><!--将指定的bean配置为切面类(即通知类),ref属性值为要指定的切面类(即通知类)对应bean的id值--><aop:aspect ref="myAdvice"><!--配置切入点--><aop:pointcut id="myPointcut" expression="execution(void at.guigu.service.BrandService.update())"/><!--配置指定切面:切点+通知--><aop:before method="method1" pointcut-ref="myPointcut"></aop:before><!--配置指定切面:切点+通知<aop:before method="method1" pointcut="execution(void at.guigu.service.BrandService.update())"></aop:before>--></aop:aspect> </aop:config>
-
-
基于注解开发的抽取方式
-
在通知类(即切面类)中定义一个方法,在该方法上使用
@Pointcut
注解定义切点表达式,然后在增强注解中进行引用即可。MyAdvive切面类(即通知类)代码更改如下package at.guigu.aop;import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect @Component public class MyAdvice {// 定义切入点:即定义公共功能在哪执行//此处代表在update()方法处执行@Pointcut("execution(void at.guigu.service.BrandService.update())")private void pt(){}// 通知方法(即增强方法)// 抛出异常后通知@Before("pt()")public void method1() {System.out.println(System.currentTimeMillis());} }
-
AOP通知类型
-
AOP通知描述了抽取的共性功能,根据共性功能抽取位置的不同,最终运行代码时要将其加入到合理的位置,AOP通知共有5种类型
-
基于XML的AOP开发
通知类型 解释 前置通知 <aop:befrore>
在原始方法(即目标方法)执行前执行 后置通知 <aop:after>
在原始方法(即目标方法)执行后执行 环绕通知 <aop:around>
可以决定是否执行原始方法(即目标方法)或修改返回值 返回后通知 <aop:after-returning>
在原始方法(即目标方法)正常执行完毕后 执行 抛出异常后通知 <aop:throwing>
只有在原始方法(即目标方法)运行抛出异常后才会执行 -
基于注解的AOP开发
通知类型 解释 前置通知 @Before(value)
在原始方法(即目标方法)执行前执行 后置通知 @After(value)
在原始方法(即目标方法)执行后执行 环绕通知 @Around(value)
可以决定是否执行原始方法(即目标方法)或修改返回值 返回后通知 @AfterReturning(value)
在原始方法(即目标方法)正常执行完毕后 执行 抛出异常后通知 @AfterThrowing(value)
只有在原始方法(即目标方法)运行抛出异常后才会执行 以上五种通知类型的value值均为切入点方法名
-
-
后置通知与返回后通知的区别
- 后置通知在原始方法(即目标方法)执行完成之后执行,无论目标方法是正常返回结果还是抛出异常,它都会执行。类似于Java中的
finally
块,后置通知通常用于执行一些资源清理工作或记录日志,无论目标方法是否成功运行。 - 返回后通知在原始方法(即目标方法)正常返回结果之后执行。如果原始方法(即目标方法)抛出异常,返回后通知不会执行
- 后置通知在原始方法(即目标方法)执行完成之后执行,无论目标方法是正常返回结果还是抛出异常,它都会执行。类似于Java中的
环绕通知
-
环绕通知
@Around
是一种功能最强大的通知类型,它允许你在原始方法(即目标方法)执行之前和之后执行自定义逻辑,甚至可以决定是否执行原始方法(即目标方法)或修改返回值。-
可通过调用
ProceedingJoinPoint
中的proceed()
方法来决定是否执行原始方法(即目标方法) -
可在原始方法(即目标方法)执行前后添加自定义逻辑
-
可以捕获原始方法(即目标方法)的返回值并在返回之前进行修改
-
可以捕获原始方法(即目标方法)抛出的异常并处理
-
-
注意事项
-
环绕通知必须依赖形参
ProceedingjoinPoint
才能实现对原始方法(即目标方法)的调用,进而实现原始方法(即目标方法)调用前后同时添加通知 -
通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法(即目标方法)的执行
-
对原始方法(即目标方法)的调用可以不接收返回值,通知方法设置成
void
即可 ,如果接收返回值,必须设定为Object
类型 -
当通知方法设置为
void
时,对原始方法(即目标方法)就无法获取返回值 -
原始方法(即目标方法)的返回值如果是
void
类型,通知方法的返回值类型可以设置成void
,也可以设置成Object
-
由于无法预知原始方法(即目标方法)运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
-
-
可能用到的接口及其对应方法
ProceedingjoinPoint
接口中的方法解释 Object proceed() throws Throwable
决定是否执行原始方法(即目标方法) Object proceed(Object[] var1) throws Throwable
决定是否执行原始方法(即目标方法),并给原始方法传入参数。(示例可见AOP通知获取数据部分) Signature getSignature()
获取包含原始方法(即目标方法)签名信息的对象,包括方法名、声明类型、参数类型等。 Signature
接口中用到的方法解释 String getName()
获取原始方法(即目标方法)名称 String toString()
获取原始方法(即目标方法)的完整签名(即切入点表达式标准格式的内容) Class getDeclaringType()
获取原始方法(即目标方法)所在类的Class对象 String getDeclaringTypeName()
获取始方法(即目标方法)所在类的全限定名
示例(基于注解形式)
本实例会以快速入门以基础进行演示
该示例以基于注解的AOP开发为例,可自行进行基于XML的AOP开发示例
-
前置通知
@Before
,aop包下的MyAdvice
类代码如下package at.guigu.aop;import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect @Component public class MyAdvice {// 定义切入点:即定义公共功能在哪执行//此处代表在update()方法处执行@Pointcut("execution(void at.guigu.service.BrandService.update())")private void pt(){}// 通知类// 前置通知:在update方法执行前执行@Before("pt()")public void method1() {System.out.println(System.currentTimeMillis());} }
-
后置通知
@After
,aop包下的MyAdvice
类代码如下package at.guigu.aop;import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect @Component public class MyAdvice {// 定义切入点:即定义公共功能在哪执行//此处代表在update()方法处执行@Pointcut("execution(void at.guigu.service.BrandService.update())")private void pt(){}// 通知类// 前置通知:在update方法执行后执行@After("pt()")public void method1() {System.out.println(System.currentTimeMillis());} }
-
环绕通知
@Around
,aop包下的MyAdvice
类代码如下package at.guigu.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect @Component public class MyAdvice {// 定义切入点:即定义公共功能在哪执行//此处代表在update()方法处执行@Pointcut("execution(void at.guigu.service.BrandService.update())")private void pt(){}// 环绕通知方法@Around("pt()")public Object method1(ProceedingJoinPoint pjp) throws Throwable {// 获取包含原始方法(即目标方法)签名信息的对象Signature signature = pjp.getSignature();// 获取原始方法(即目标方法)名称String name = signature.getName();// 获取原始方法(即目标方法)的完整签名String string = signature.toString();// 获取原始方法(即目标方法)所在类的Class对象Class declaringType = signature.getDeclaringType();// 获取始方法(即目标方法)所在类的全限定名String declaringTypeName = signature.getDeclaringTypeName();System.out.println(name);System.out.println(string);System.out.println(declaringType);System.out.println(declaringTypeName);// 目标方法前的自定义逻辑System.out.println("前:" + System.currentTimeMillis());// 调用原始操作(即目标方法)Object ret = pjp.proceed();// 目标方法后的自定义逻辑System.out.println("后:" + System.currentTimeMillis());return ret;} }
注意:
1.在该通知类中抛出异常是因为不知道目标是否有异常,所以在调用原始操作时会强制抛出异常
2.获取原始方法(即目标方法)所在类的Class对象时,若原始方法是接口中的则最终显示的是
Interface
而不是Class
,如上图所示 -
抛出异常后通知
@AfterThrowing
,aop包下的MyAdvice
类代码如下package at.guigu.aop;import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect @Component public class MyAdvice {// 定义切入点:即定义公共功能在哪执行//此处代表在update()方法处执行@Pointcut("execution(void at.guigu.service.BrandService.update())")private void pt(){}// 通知类// 抛出异常后通知@AfterThrowing("pt()")public void method1() {System.out.println(System.currentTimeMillis());} }
-
BrandServiceImpl类代码如下(存在异常)
package at.guigu.service.impl;import at.guigu.service.BrandService; import org.springframework.stereotype.Service;@Service("bransServiceImpl") public class BrandServiceImpl implements BrandService {@Overridepublic void save() {System.out.println(System.currentTimeMillis());System.out.println("bookDao save...");}@Overridepublic void update() {int i = 1/0;System.out.println("bookDao update...");} }
此时运行截图如下
-
BrandServiceImpl类代码如下(不存在异常)
package at.guigu.service.impl;import at.guigu.service.BrandService; import org.springframework.stereotype.Service;@Service("bransServiceImpl") public class BrandServiceImpl implements BrandService {@Overridepublic void save() {System.out.println(System.currentTimeMillis());System.out.println("bookDao save...");}@Overridepublic void update() {System.out.println("bookDao update...");} }
此时运行截图如下
-
案例1:测量业务层接口万次执行效率(基于注解形式)
需求:任意业务层接口执行均可显示其执行效率(执行时长)
分析:
业务功能:业务接口执行前后分别记录时间,求差值得到执行效率
通知类型选择前后均可增强的类型——环绕通知
注意:本案例以Spring集成MyBatis部分的示例为例,并集成JUnit进行测试,重复步骤可见Spring集成MyBatis部分代码示例,此处只写需要添加代码或未重复部分的步骤示例
-
Step1: 导入坐标
-
导入Spring基础坐标:spring-context
-
导入Spring提供的监听器
ContextLoaderListener
的相关坐标:spring-web -
导入Spring集成Web环境相关坐标:servlet、jsp
-
导入Spring注解相关坐标:Annotation
-
导入与AOP相关的坐标:aop、aspectj
-
AOP坐标会在导入spring-context坐标后系统自动导入,如图所示
-
-
导入数据库相关坐标:mysql、数据源坐标(druid、cp30)
-
导入Spring集成JUnit相关坐标:junit、spring-test
-
导入Spring集成MyBatis相关坐标:mybatis、spring-jdbc、mybatis-spring
-
-
Step2: 在Spring的核心配置类
SpringConfiguration
中添加@EnableAspectJAutoProxy
注解,完整代码如下package at.guigu.config;import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import;// 该注解代表该类是Spring的核心配置类 @Configuration // 配置注解的组件扫描<context:component-scan base-package="at.guigu"></context:component-scan> @ComponentScan("at.guigu") @MapperScan("at.guigu.dao") // 引入拆分配置文件<import resource="applicationContext-xxx.xml"/> @Import({DataSourceConfiguration.class, MyBatisConfiguration.class}) @EnableAspectJAutoProxy public class SpringConfiguration { }
-
Step3: 创建一个与三层架构包同级的aop包,并在该包下创建
MyAdvice
通知类,代码如下- Step3-1: 在该类中创建一个通知(即方法),并写入作为公共功能的内容
- Step3-2: 在该类中用
@Pointcut
定义切入点- 切入点的定义依托一个不具有实际意义的方法(即无参数、无返回值,方法体内无实际逻辑)
- Step3-3: 绑定切入点与通知的关系,并指定通知的执行位置。(即定义切面)
- Step3-4: 为通知类添加
@Component
以及@Aspect
注解@Component
:将切面类作为Spring容器中的一个Bean来管理@Aspect
:标记该类为切面类
- Step3-5: 通过
ProceedingJoinPoint
接口中的getSignature
方法获取原始方法签名信息对象,并调用该对象中的getName
方法来获取当前原始方法的名称,以此来判断业务层中不同方法的万次执行效率
package at.guigu.aop;import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect @Component public class MyAdvice {// 匹配任意返回类型,at包下的guigu包下的任意子包下的以Service为结尾的类或接口中的任意数量参数的任意方法// 即匹配业务层下的所有方法@Pointcut("execution(* at.guigu.*.*Service.*(..))")private void servicePt(){}// 环绕通知方法@Around("servicePt()")public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {// 获取包含原始方法(即目标方法)签名信息的对象,包括方法名、声明类型、参数类型等。Signature signature = pjp.getSignature();// 获取方法名称String name = signature.getName();System.out.println(name);long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {Object ret = pjp.proceed();}long end = System.currentTimeMillis();System.out.println(name + "执行一万遍的效率为:" + (end - start) + "ms");} }
-
Step4: 在test包下创建三层架构包的业务层service包,并在该包中创建
BrandServiceTest
测试类,完整代码如下- Step4-1: 该类要使用
@Runwith
注解替换原来的运行器,并设置新的类运行器- 其属性为
SpringRunner.class
或SpringJUnit4ClassRunner.class
:用于集成 Spring 测试框架
- 其属性为
- Step4-2: 该类使用
@ContextConfiguration
指定Spring配置文件或Spring配置类- 指定Spring配置文件:
@ContextConfiguration("classpath:applicationContext.xml")
- 指定单个Spring配置类:
@ContextConfiguration(classes = SpringConfiguration.class)
- 指定多个Spring配置类:
@ContextConfiguration(classes = {SpringConfiguration.class,...})
- 指定Spring配置文件:
package at.guigu.service;import at.guigu.config.SpringConfiguration; import at.guigu.pojo.Brand; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.util.List;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class BrandServiceTest {@Autowiredprivate BrandService brandService;@Testpublic void testGetAll( ) {List<Brand> brands = brandService.getAll();System.out.println(brands);}@Testpublic void testGetById() {Brand brand = brandService.getById(1);} }
- Step4-1: 该类要使用
AOP通知获取数据(基于注解形式)
-
AOP通知获取的数据主要有三种
-
获取切入点方法的参数
-
环绕通知利用
ProceedingJoinPoint
接口获取ProceedingJoinPoint
接口是JoinPoint
接口的子接口,所以在环绕通知中直接用ProceedingJoinPoint
调用getArgs()
即可 -
其它四种通知类型利用
JoinPoint
接口获取JoinPoint
接口解释 Object[] getArgs()
获取原始方法(即目标方法)执行时接收的实参值
-
-
获取切入点方法的返回值
- 环绕通知
- 利用
ProceedingjoinPoint
接口中的proceed()
方法即可。此处不在做演示
- 利用
- 返回后通知可以获取
- 环绕通知
-
获取切入点方法的异常
- 环绕通知
- 抛出异常后通知
-
获取切入点方法的参数步骤
-
获取切入点方法的参数——其它四种通知类型利用
JoinPoint
接口获取 (此处仅以前置通知为例,其余三种一样)-
给业务层中的update方法添加参数,以供测试,代码截图如下
-
通知类MyAdvice代码如下
package at.guigu.aop;import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;import java.util.Arrays;@Aspect @Component public class MyAdvice {// 定义切入点:即定义公共功能在哪执行//此处代表在update()方法处执行@Pointcut("execution(void at.guigu.service.BrandService.update(..))")private void pt(){}// 通知类// 前置通知:在update方法执行前执行@Before("pt()")public void method1(JoinPoint jp) {// 获取原始方法(即目标方法)执行时接收的实参值Object[] args = jp.getArgs();// 将其转化为字符串数组System.out.println(Arrays.toString(args));System.out.println(System.currentTimeMillis());} }
-
-
获取切入点方法的参数——环绕通知类型利用
ProceedingJoinPoint
接口获取-
给业务层中的update方法添加参数,以供测试,代码截图如下
由图可看出原始方法接收的实参值为
1
和zhangsan
-
通知类MyAdvice代码如下
在获取到实参值后可对实参值进行处理后作为参数传入
proceed(Object[] var1)
方法中修改原始方法的参数值并执行原始方法package at.guigu.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;import java.util.Arrays;@Aspect @Component public class MyAdvice {// 定义切入点:即定义公共功能在哪执行//此处代表在update()方法处执行@Pointcut("execution(void at.guigu.service.BrandService.update(..))")private void pt(){}// 环绕通知方法@Around("pt()")public Object method1(ProceedingJoinPoint pjp) throws Throwable {// 获取原始方法执行时接收的实参值Object[] args = pjp.getArgs();System.out.println("原始方法执行时接收的参数值为:" + Arrays.toString(args));// 对实参值进行处理args[0] = 200;// 目标方法前的自定义逻辑System.out.println("前:" + System.currentTimeMillis());// 调用原始操作(即目标方法)Object ret = pjp.proceed(args);// 目标方法后的自定义逻辑System.out.println("后:" + System.currentTimeMillis());return ret;} }
-
获取切入点方法的返回值步骤
-
注意
- 仅以返回后通知为例,因为环绕利用
ProceedingjoinPoint
接口中的proceed()
方法即可。此处不在演示环绕通知
- 仅以返回后通知为例,因为环绕利用
-
Step1: 给业务层中的update方法添加参数,以供测试,代码截图如下
-
Step2: 通知类MyAdvice代码如下
-
Step2-1: 给返回后通知方法传入一个接收返回值的参数
Object[] ret
-
Step2-2: 给返回后通知注解
@AfterReturning(value,returning)
添加returning
属性,其作用是将原始方法的返回值传递给通知方法中的Object[] ret
。注意:该属性值名必须与接收返回值的参数名一致,否则报错 -
Step2-3: 若返回后通知方法也要获取原始方法的参数的话,则需要将
JoinPoint
接口作为参数,且该参数必须在接收返回值参数前面 -
完整代码如下
package at.guigu.aop;import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;import java.util.Arrays;@Aspect @Component public class MyAdvice {// 定义切入点:即定义公共功能在哪执行//此处代表在update()方法处执行@Pointcut("execution(String at.guigu.service.BrandService.update(..))")private void pt(){}// 通知方法// 返回后通知:在update方法执行后执行@AfterReturning(value = "pt()", returning = "ret")public void method1(JoinPoint jp, String ret) {// 获取原始方法(即目标方法)执行时接收的实参值Object[] args = jp.getArgs();// 将其转化为字符串数组System.out.println(Arrays.toString(args));// 打印原始方法的返回值System.out.println(ret);System.out.println(System.currentTimeMillis());} }
-
获取切入点方法的异常的步骤
-
环绕通知: 将原来的异常抛出改为利用try…catch进行异常捕获处理即可,如下图所示
-
抛出异常后通知:
-
Step1: 将业务层中update方法更改为如图所示,变为存在异常,以供测试
-
Step2: 通知类MyAdvice代码如下
-
Step2-1: 给返回后通知方法传入一个接收异常的参数
Throwable e
-
Step2-2: 给返回后通知注解
@AfterReturning(value,returning)
添加returning
属性,其作用是将异常传递给通知方法中的Throwable e
。注意:该属性值名必须与接收异常的参数名一致,否则报错
package at.guigu.aop;import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect @Component public class MyAdvice {// 定义切入点:即定义公共功能在哪执行//此处代表在update()方法处执行@Pointcut("execution(void at.guigu.service.BrandService.update())")private void pt(){}// 通知类// 抛出异常后通知@AfterThrowing(value = "pt()",throwing = "e")public void method1(Throwable e) {System.out.println(System.currentTimeMillis());} }
-
-
Spring事务控制
-
作用:在数据访问层(即持久层)或业务层中通过
PlatformTransactionManager
接口(即事务平台管理器)保障一系列的数据库操作要么同时成功要么同时失败- Spring内部提供了这个接口的实现类
DataSourceTransactionManager
和HibernateTransactionManager
- Spring内部提供了这个接口的实现类
-
事务控制有两种
- 编程式事务控制
- 手动编写事务管理代码,使用
PlatformTransactionManager
接口管理事务
- 手动编写事务管理代码,使用
- 声明式事务控制
- 不需要手动编写事务管理代码,使用注解(如
@Transactional
)或 XML 配置声明事务即可 - 它是不侵入式的,即业务逻辑对象不会意识到正在事务管理之中,若想要改变事务管理策略的话,只需要在定义文件中重新配置即可。
- 在不需要事务管理时,只要修改配置文件即可移除事务管理服务,无需改变代码重新编译,维护起来更方便
- 声明式事务控制的本质是AOP完成的,对方法前后进行拦截,在执行原始方法(即目标方法)之前开启事务,在执行完原始方法(即目标方法)之后根据执行情况来提交或回滚事务
- 不需要手动编写事务管理代码,使用注解(如
- 编程式事务控制
编程式事务控制相关对象
- 编程式事务控制三大对象
PlatformTransactionManager
接口TransactionDefinition
接口TransactionStatus
接口
- 注意
- 前两个编程式事务控制对象不需要去手动编程,只需要在配置文件中进行配置
- 最后一个编程式事务控制对象是被动封装事务的状态信息,状态信息会随着程序的运行自动改变,所以不需要去配置
PlatformTransactionManager
接口
-
PlatformTransactionManager
接口是Spring的事务管理器对象,它提供了我们常用的操作事务的方法PlatformTransactionManager
接口中的方法解释 TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; 获取事务状态信息 void commit(TransactionStatus status) throws TransactionException
提交事务 void rollback(TransactionStatus status) throws TransactionException
回归事务 -
不同的持久层技术使有不同的实现类,比如
- 若持久层使用的技术是JDBC或MyBatis时,实现类为:
org.springframework.jdbc.datasource.DataSourceTransactionManager
- 若持久层使用的技术是Hibernate时,实现类为:
org.springframework.orm.hibernate5.HibernateTransactionManager
- 若持久层使用的技术是JDBC或MyBatis时,实现类为:
TransactionDefinition
接口
-
TransactionDefinition
接口是事务的定义信息对象,用于定义事务的属性和配置选项,可能用到的方法如下TransactionDefinition
接口中的方法解释 default int getPropagationBehavior()
获取事务的传播行为,决定了当前事务如何与外部事务交互。默认为0,即若当前没事务则创建一个新事务;若有事务则加入到现有事务中。 default int getIsolationLevel()
获取事务的隔离级别 default int getTimeout()
获取事务的超时时间(单位:秒),如果事务在指定时间内没有完成,Spring 会自动回滚事务。默认值为-1,代表无超时时间限制 default boolean isReadOnly()
事务是否为只读事务,若是则返回 true
,默认为false
。建议查询时设置为true
-
事务的传播行为有七种
-
REQUIRED
(默认值0):若当前有事务,则加入到现有事务中;若当前无事务,则新建一个事务。 -
SUPPORTS
(1):若当前有事务则支持当前事务,加入到当前事务;若当前无事务,则以非事务方式执行 -
MANDATORY
(2):若当前有事务,则加入到现有事务中;若当前无事务,则抛出异常 -
REQUERS_NEW
(3):不论当前是否有事务都会去新建一个事务,若当前存在事务,就会把当前事务挂起。 -
NOT_SUPPORTED
(4):不论当前是否有事务都会以非事务方式运行,若当前存在事务,就会把当前事务挂起。 -
NEVER
(5):以非事务方式运行,若当前存在事务,就会抛出异常 -
NESTED
(6):若当前有事务,则会在当前事务的内部新建一个事务来执行;若当前无事务,则会新建一个事务- 注意:内部事务与其外部事务相互独立,均有自己的提交和回滚规则
-
理解示例如下(以默认值为例,其它类似)
- 当a业务方法调用b业务方法时,b业务方法会看a是否有事务 ,若a有事务则加入到a事务中;若a无事务则b就会创建一个新事务
-
-
事务的隔离级别有四种,主要用于解决事务并发所产生的问题,即脏读、不可重复度(虚读)、幻读
-
读未提交
READ_UNCOMMITTED
(1):并发产生的三种问题均不能解决 -
读已提交
READ_COMMITTED
(Oracle默认)(2):只能解决脏读问题 -
可重复读
REPEATABLE_READ
(MySQL默认)(4):解决脏读、不可重复读问题 -
串行化
SERIALIZABLE
(8):并发产生的三种问题均可解决 -
以上四种隔离级别从小到大,安全性越来越高,但是效率越来越小,所以一般使用MySQL默认的隔离级别即可。
-
TransactionStatus
接口
-
TransactionStatus
接口是事务的状态对象,可用于查询当前事务具体的运行状态,可能用到的方法如下TransactionStatus
接口中的方法解释 boolean hasSavepoint()
检查当前事务是否有保存点(即是否存储的有回滚点) boolean isCompleted()
检查当前事务是否已被提交或回滚 boolean isNewTransaction()
检查当前事务是否是一个新的事务 boolean isRollbackOnly()
查当前事务是否标记为仅回滚状态 -
保存点:允许在事务执行过程中设置某个特定的状态,以便在需要时回滚到这个状态。保存点通常用于 嵌套事务 或复杂事务场景中,当某些操作失败时,可以回滚到某个中间状态,而不是回滚整个事务。
快速入门
- 案例:银行账户间转账业务
环境准备相同步骤
-
Step1: 导入坐标
-
导入Spring基础坐标:spring-context
-
导入Spring提供的监听器
ContextLoaderListener
的相关坐标:spring-web -
导入Spring集成Web环境相关坐标:servlet、jsp
-
导入Spring注解相关坐标:Annotation
-
导入与AOP相关的坐标:aop、aspectj
-
AOP坐标会在导入spring-context坐标后系统自动导入,如图所示
-
-
导入事务相关坐标:spring-tx
-
导入数据库相关坐标:mysql、数据源坐标(druid、cp30)
-
导入Spring集成JUnit相关坐标:junit、spring-test
-
导入Spring集成MyBatis相关坐标:mybatis、spring-jdbc、mybatis-spring
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.example</groupId><artifactId>SpringDemo</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>SpringTranXmlDemo</artifactId><packaging>war</packaging><name>SpringTranDemo Maven Webapp</name><url>http://maven.apache.org</url><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><!--===================Spring基础坐标=======================--><!--spring坐标--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.1.6</version></dependency><!--===================Spring自带监听器ContextLoaderListener所需坐标=======================--><!--spring-web--><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.25.RELEASE</version></dependency><!--===================Spring集成Web环境相关坐标=======================--><!-- servlet--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><!--jsp--><dependency><groupId>javax.servlet.jsp</groupId><artifactId>javax.servlet.jsp-api</artifactId><version>2.3.3</version><scope>provided</scope></dependency><!--===================Spring注解相关坐标=======================--><!--Annotation坐标--><dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>1.3.2</version></dependency><!--=====================AOP相关坐标=========================--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.22.1</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>6.1.15</version></dependency><!--=====================数据库相关坐标=========================--><!--mysql坐标--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><!--druid坐标--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.18</version></dependency><!--c3p0坐标--><dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.5</version></dependency><!--===================Spring集成junit相关坐标=======================--><!--junit坐标--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><!--spring-test坐标--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>6.1.6</version><scope>test</scope></dependency><!--=====================MyBatis相关坐标=========================--><!--MyBatis坐标--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.16</version></dependency><!--mybatis-spring--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>3.0.3</version></dependency><!--spring-jdbc--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.1.10</version></dependency></dependencies><build><finalName>SpringTranDemo</finalName><plugins><!-- Tomcat插件 --><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version></plugin></plugins></build> </project>
-
-
Step2: 右键源代码配置文件目录(即资源文件
resources
)→New
→File
,创建properties配置文件,博主文件名为jdbc.properties
,该配置文件代码如下- 注意: properties配置文件中配置的各个属性前必须添加个
id.
(即id.属性
,比如:属性url
就设置为id.url
,博主设置的为jdbc.url
),以供Spring配置文件可以使用属性占位符${}
语法引用这些属性
#driverClassName代表数据库驱动,后跟驱动全类名(在MySQL驱动jar包下的META-INF下的services文件夹下的java.sql.Driver文件内) jdbc.driverClassName=com.mysql.cj.jdbc.Driver # 数据库连接URL jdbc.url=jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai # 数据库用户名 jdbc.username=root # 数据库密码 jdbc.password=123456 # 初始化连接数量---即容器中初始的数据库连接数量 jdbc.initialSize=5 # 最大活跃连接数量---容器中初始为5个,但若5个用完了,此时可以在申请5个数据库连接数量 #也就是说容器中最多存放10个数据库连接 jdbc.maxActive=10 # 获取连接时的最大等待时间,单位:毫秒。---与数据库进行连接时若超过3s仍未连接成功,则会报错 jdbc.maxWait=3000 #最小空闲连接数量---minIdle=5 # 配置检测连接是否有效的SQL,可以是一个查询语句,如果不指定则默认为"SELECT 1"---validationQuery=SELECT 1 # 是否开启自动提交事务---defaultAutoCommit=true
- 注意: properties配置文件中配置的各个属性前必须添加个
-
Step3: 创建数据库表account 并使IDEA与数据库建立连接 ,SQL代码如下
DROP TABLE IF EXISTS account; #创建账户表 CREATE TABLE account (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(10),money DOUBLE(10,2) ); #添加数据 INSERT INTO account(name, money) VALUES ('张三', 1000), ('李四', 1000); SELECT * FROM account;
基于XML的声明式事务控制
环境准备
-
Step1: 创建一个与三层架构包同级的pojo包,并在该包下创建实体类
User
,代码所如下package at.guigu.pojo;public class User {private String name;private double money;public User() {}public User(String name, double money) {this.name = name;this.money = money;}public String getName() {return name;}public void setName(String name) {this.name = name;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", money=" + money +'}';} }
-
Step2: 右键源代码配置文件目录(即资源文件
resources
)→New
→File
,创建MyBatis核心配置文件(名为mybatis-config.xml
),代码如下:<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><!--设置别名--><typeAliases><package name="at.guigu.pojo"/></typeAliases></configuration>
-
Step3: 右键源代码配置文件目录(即资源文件
resources
)→New
→XML Configuration File
→Spring Config
,创建Spring核心配置文件(名为applicationContext.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"></beans>
- Step3-1: 使用
context
命名空间加载jdbc.properties
文件(前提:需引入context
命名空间和约束路径)context
命名空间:xmlns:context="http://www.springframework.org/schema/context"
context
约束路径:http://www.springframework.org/schema/context
、http://www.springframework.org/schema/context/spring-context.xsd
- Step3-2: 配置数据源对应的bean
- Step3-3: 配置MyBatis的SqlSessionFactory
- 配置数据源
- 配置MyBatis核心配置文件(注意:若有的配置必须通过MyBatis核心配置文件配置时,则需要该步)
- 配置别名
- Step3-4: 引入dao包下所有接口对应的SQL映射文件
- 此时Spring会进行持久层扫描,自动生成该层中对应接口的bean
- Step3-5: 引入tx命名空间和约束路径(作用:用来配置平台事务管理器以及事务增强)
tx
命名空间:xmlns:tx="http://www.springframework.org/schema/tx"
- tx约束路径:
http://www.springframework.org/schema/tx
、http://www.springframework.org/schema/tx/spring-tx.xsd
- Step3-6: 配置平台事务管理器
- 由于持久层此时使用的技术是JDBC或MyBatis时,所以实现类为:
org.springframework.jdbc.datasource.DataSourceTransactionManager
- 由于持久层此时使用的技术是JDBC或MyBatis时,所以实现类为:
- Step3-7: 配置通知:声明式事务的增强
<?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"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!--使用`context`命名空间加载 `properties` 文件--><context:property-placeholder location="classpath:jdbc.properties"/><!--Druid对应的bean--><bean id="dataSourceDruid" class="com.alibaba.druid.pool.DruidDataSource"><!--使用属性占位符`${}`语法引用properties文件中的属性--><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!--配置MyBatis的SqlSessionFactory--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--配置数据源--><property name="dataSource" ref="dataSourceDruid"/><!--加载MyBatis的核心配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"/><!--配置别名--><property name="typeAliasesPackage" value="at.guigu.pojo"/></bean><!--引入dao包下所有接口对应的SQL映射文件即MyBatis 持久层扫描,会自动生成该层中对应接口的bean--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="at.guigu.dao"/></bean><!--配置平台事务管理器--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSourceDruid"/></bean><!--配置通知:声明式事务的增强--><!--transaction-manager属性值为对应平台事务管理器的bean的id--><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="*"/></tx:attributes></tx:advice></beans>
- Step3-1: 使用
-
Step3: 创建三层架构包,且初始代码分别如下
-
在持久层dao包下创建UserDao接口
package at.guigu.dao;public interface UserDao { }
-
在该Maven项目的源代码配置文件目录(即main包下的resources目录下)创建多级目录,然后在与
UserDao
接口对应的目录下创建 SQL映射文件UserDao.xml
,如图所示,SQL映射文件代码如下所示<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:名称空间--> <mapper namespace="at.guigu.dao.UserDao"><!--结果映射--><resultMap id="userResultMap" type="user"><!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可--><result column="name" property="name"/><result column="money" property="money"/></resultMap> </mapper>
-
在业务层service包下创建
UserService
类,初始代码如下package at.guigu.service;import at.guigu.dao.UserDao;public class UserService {private UserDao userDao;public void setUserDao(UserDao userDao) {this.userDao = userDao;} }
注意:此处使用的是setter方法注入,所以必须在业务层中用setter方法将IOC容器中自动生成的持久层接口对应的bean注入到业务层对应的类中
-
在表现层web包下创建BrandServlet类,初始代码如下
package at.guigu.web;import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;public class UserServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
-
-
Step4: 在web项目核心目录(即
WEB-INF
)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener
监听器、web配置。完整代码如下<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--全局初始化参数--><context-param><!--定义参数的名称,必须是唯一的--><param-name>contextConfigLocation</param-name><!--定义参数的值--><param-value>classpath:applicationContext.xml</param-value></context-param><!--监听器--><!--配置Spring所提供的`ContextLoaderListener` 监听器--><listener><!--监听器类的全限定名--><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--声明一个Servlet--><servlet><!--声明的Servlet的类名--><servlet-name>UserServlet</servlet-name><!--声明的Servlet的全限定名--><servlet-class>at.guigu.web.UserServlet</servlet-class></servlet><!--将URL模式映射到特定的Servlet上(即UserServlet)--><servlet-mapping><!--指定的Servlet的类名--><servlet-name>UserServlet</servlet-name><!--给指定的Servlet设置url,相当于@WebServlet("/userServlet")--><url-pattern>/userServlet</url-pattern></servlet-mapping> </web-app>
银行账户间转账业务
-
Step1: 在dao包下的
UserDao
接口中写入查询方法,然后在对应的SQL映射文件中写入对应SQL语句-
UserDao
接口代码如下package at.guigu.dao;import at.guigu.pojo.User; import org.apache.ibatis.annotations.Param; import java.util.List;public interface UserDao {void inMoney(@Param("name")String name, @Param("money")Double money);void outMoney(@Param("name")String name, @Param("money")Double money);List<User> all(); }
注意:定义含多个参数的接口方法时,要通过
@Param("参数")
注解来将对应的方法参数放到SQL映射文件中SQL语句对应的参数占位符上,其中注解参数名称要和SQL映射文件中SQL语句中的参数占位符名称对应一致 -
SQL映射文件UserDao.xml文件代码如下
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:名称空间--> <mapper namespace="at.guigu.dao.UserDao"><!--结果映射--><resultMap id="userResultMap" type="user"><!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可--><result column="name" property="name"/><result column="money" property="money"/></resultMap><update id="inMoney">update account set money = money + #{money} where name = #{name};</update><update id="outMoney">update account set money = money - #{money} where name = #{name};</update><select id="all" resultMap="userResultMap">select * from account;</select> </mapper>
-
-
Step2: 在业务层service包下的
UserService
类来调用dao
包下的UserDao
接口中的方法,代码如下package at.guigu.service;import at.guigu.dao.UserDao; import at.guigu.pojo.User;import java.util.List;public class UserService {private UserDao userDao;public void setUserDao(UserDao userDao) {this.userDao = userDao;}public void inMoney(String name, Double money) {userDao.inMoney(name, money);}public void outMoney(String name, Double money) {userDao.outMoney(name, money);}public void transfer(String out, String in, Double money) {this.outMoney(out, money);this.inMoney(in, money);}public List<User> getAll() {return userDao.all();} }
-
Step3: 在Spring的核心配置文件中配置业务层对应的bean,并将持久层对应的bean依赖注入到该业务层中;同时配置事务的aop织入,完整代码如下
- Step3-1: 在Spring的核心配置文件中引入AOP的命名空间:
xmlns:aop="http://www.springframework.org/schema/aop"
- Step3-2: 在Spring的核心配置文件中引入AOP的约束路径:
http://www.springframework.org/schema/aop
、http://www.springframework.org/schema/aop/spring-aop.xsd
- Step3-3: 配置业务层bean(该示例并未创建持久层,所以不用配置持久层bean)
- Step3-4: 配置切面类(即通知类)对应的bean
- 此处为声明式事务的增强,已在环境准备中完成该步
- Step3-5: 配置织入关系:将对应通知方法(即增强方法)应用到对应目标对象的对应原始方法上
- 配置普通增强使用
<aop:aspect>
标签,可详见AOP部分内容 - 配置事务的增强使用
<aop:advisor>
标签
- 配置普通增强使用
<?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"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--使用`context`命名空间加载 `properties` 文件--><context:property-placeholder location="classpath:jdbc.properties"/><!--Druid对应的bean--><bean id="dataSourceDruid" class="com.alibaba.druid.pool.DruidDataSource"><!--使用属性占位符`${}`语法引用properties文件中的属性--><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!--配置MyBatis的SqlSessionFactory--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--配置数据源--><property name="dataSource" ref="dataSourceDruid"/><!--加载MyBatis的核心配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"/><!--配置别名--><property name="typeAliasesPackage" value="at.guigu.pojo"/></bean><!--引入dao包下所有接口对应的SQL映射文件即MyBatis 持久层扫描,会自动生成该层中对应接口的bean--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="at.guigu.dao"/></bean><!--配置平台事务管理器--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSourceDruid"/></bean><!--配置UserService实现类bean 即目标对象--><bean id="userService" class="at.guigu.service.UserService"><property name="userDao" ref="userDao"/></bean><!--配置通知类(即切面类)对应的bean:声明式事务的增强--><!--transaction-manager属性值为对应平台事务管理器的bean的id--><tx:advice id="txAdvice" transaction-manager="transactionManager"><!--配置事务属性--><tx:attributes><!--name属性指定需要增强的原始方法--><tx:method name="*"/></tx:attributes></tx:advice><!--配置织入关系:即将对应通知方法(即增强方法)应用到对应目标对象的对应原始方法上--><aop:config><!--配置指定切面(切点+通知)方式一--><!--配置切入点--><aop:pointcut id="myPointcut" expression="execution(* at.guigu.service.*.*(..))"/><!--配置用纸--><aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/><!--配置指定切面(切点+通知)方式二<aop:advisor advice-ref="txAdvice" pointcut="execution(* at.guigu.service.*.*(..))"/>--></aop:config></beans>
配置指定切面的两种方式可详见切入点表达式的抽取的内容
- Step3-1: 在Spring的核心配置文件中引入AOP的命名空间:
-
Step4: 在表现层web包下的BrandServlet类中来调用业务层service包中的BrandService类中的方法,代码如下:
package at.guigu.web;import at.guigu.pojo.User; import at.guigu.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List;public class UserServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1 获取最大域ServletContext对象ServletContext servletContext = request.getServletContext();//等同于ServletContext servletContex t = request.getSession().getServletContext();// 2 获取应用上下文对象(即IOC容器)ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);//3 获取beanUserService userService = app.getBean(UserService.class);//4 调用方法执行SQL语句userService.transfer("张三", "李四", 500.00);List<User> users = userService.getAll();for (User user : users) {System.out.println(user);}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
-
Tomcat运行截图如下
-
无异常时,操作全部执行。张三、李四分别由原来的1000变为了500、1500,如图所示
-
此时若将业务层的代码中添加一个异常则操作就会全不执行,运行截图如下
-
基于注解的声明式事务控制
环境准备
-
Step1: 创建一个与三层架构包同级的pojo包,并在该包下创建实体类
User
,代码所如下package at.guigu.pojo;import org.apache.ibatis.type.Alias;@Alias("user") public class User {private String name;private double money;public User() {}public User(String name, double money) {this.name = name;this.money = money;}public String getName() {return name;}public void setName(String name) {this.name = name;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", money=" + money +'}';} }
-
Step2: 创建一个与三层架构包同级的
config
包,并在该包下创建拆分配置文件对应的数据源拆分类DataSourceConfiguration
,代码如下(以Druid为例)-
Step2-1: 创建数据源bean
-
Step2-1: 事务管理器配置
package at.guigu.config;import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource;// 分配置文件对应的类不用配置@Configuration以及@ComponentScan注解 // 加载properties配置文件<context:property-placeholder location="classpath:jdbc.properties"/> @PropertySource("classpath:jdbc.properties") public class DataSourceConfiguration {@Value("${jdbc.driverClassName}")private String driverClassName;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;/*** Druid对应的bean* Spring会将当前方法的返回值以指定的id存储到Spring的IOC容器中* @return* @throws Exception*/@Bean("dataSourceDruid")public DataSource getDruidDataSource() throws Exception{// 创建数据源对象DruidDataSource dataSource = new DruidDataSource();// 设置数据源基本连接数据dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}// 事务管理器配置@Beanpublic PlatformTransactionManager transactionManager(@Qualifier("dataSourceDruid") DataSource dataSource) {DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return transactionManager;} }
注意:
- 在以上代码示例中,
transactionManager
方法通过参数进行bean的依赖注入时,加上了@Qualifier("dataSourceDruid")
注解是为了让其精确匹配,此处可以不加,因为此时IOC容器中只有一个DataSource对应的bean,系统会自动去IOC容器中判断是否存在DataSource对应的bean,若有则直接将其作为参数注入。当IOC容器中不只有一个DataSource对应的bean时,此时就需要@Qualifier
注解来指明参数注入的是哪个数据源bean - 若
@bean
注解未显式指定数据源名称时,其在IOC容器中的唯一标识为方法名,即getDruidDataSource
- 事务管理器配置中注入的数据源bean必须与
MyBatisConfiguration
配置类中注入的数据源bean是同一一个
-
-
Step3: 在config包下创建拆分配置文件MyBatis对应的拆分类
MyBatisConfiguration
,代码如下package at.guigu.config;import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.Bean;import javax.sql.DataSource;public class MyBatisConfiguration {@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();// 相当于设置别名<package name="at.guigu.pojo"/>sqlSessionFactoryBean.setTypeAliasesPackage("at.guigu.pojo");// 相当于配置数据库连接信息sqlSessionFactoryBean.setDataSource(dataSource);return sqlSessionFactoryBean;}// 映射扫描配置类,相当于引入dao包下所有接口对应的SQL映射文件<package name="at.guigu.dao"/>@Beanpublic MapperScannerConfigurer mapperScannerConfigurer() {MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();mapperScannerConfigurer.setBasePackage("at.guigu.dao");return mapperScannerConfigurer;} }
-
Step4: 在config包下创建Spring主配置文件对应的主类
SpringConfiguration
,并引入分配置文件对应的拆分类DataSourceConfiguration
以及MyBatisConfiguration
,代码如下-
Step4-1: 启用Spring基于AspectJ 注解驱动的AOP功能——在Spring的核心配置类中添加
@EnableAspectJAutoProxy
注解 -
Step4-2: 开启注解式事务驱动——在Spring的核心配置类中添加
@EnableTransactionManagement
注解
package at.guigu.config;import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; import org.springframework.transaction.annotation.EnableTransactionManagement;// 该注解代表该类是Spring的核心配置类 @Configuration // 配置注解的组件扫描<context:component-scan base-package="at.guigu"></context:component-scan> @ComponentScan("at.guigu") @MapperScan("at.guigu.dao") // 引入拆分配置文件<import resource="applicationContext-xxx.xml"/> @Import({DataSourceConfiguration.class, MyBatisConfiguration.class}) // 启用Spring基于AspectJ 注解驱动的AOP功能 @EnableAspectJAutoProxy // 开启注解式事务驱动 @EnableTransactionManagement public class SpringConfiguration { }
-
-
Step5: 创建三层架构包,且初始代码分别如下
- 在持久层dao包下创建UserDao接口
package at.guigu.dao;import org.apache.ibatis.annotations.Mapper;import java.util.List; @Mapper public interface UserDao {}
@Mapper
注解作用: 用于标记单个Mapper接口,让MyBatis生成它的实现类并注入到Spring容器中。也就是说此时不需要创建持久层的实现类,有IOC容器自动创建,其唯一标识id为对应接口名首字母大写(即userDao
)该注解也可以使用
@MapperScan(at.guigu.dao)
来代替,不过该注解需写在Spring的核心配置文件中,表示: 用于扫描指定包中的所有接口,将它们自动注册为Spring的Bean并作为Mapper以上两个注解在做项目时可根据实际情况选择
- 在该Maven项目的源代码配置文件目录(即main包下的resources目录下)创建多级目录,然后在与
UserDao
接口对应的目录下创建 SQL映射文件UserDao.xml
,如图所示,SQL映射文件代码如下所示
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:名称空间--> <mapper namespace="at.guigu.dao.UserDao"><!--结果映射--><resultMap id="brandResultMap" type="user"><!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可--><result column="name" property="name"/><result column="name" property="name"/></resultMap> </mapper>
注意:在MyBatis配置类的形式中,
sqlSessionFactoryBean.setTypeAliasesPackage()
方法设置别名无效,目前还未知原因,所以结果映射中的type="user"
会标红报错,所以解决办法为:利用@Alias("别名")
注解为类的全类名设置类型别名-
在业务层service包下创建
UserService
类,初始代码如下package at.guigu.service;import at.guigu.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;@Service("userService") public class UserService {@Autowiredprivate UserDao userDao; }
-
在表现层web包下创建UserServlet类,初始代码如下
package at.guigu.web;import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException;public class UserServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
-
Step4: 在web项目核心目录(即
WEB-INF
)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener
监听器、web配置。完整代码如下<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--配置Spring配置类的全局初始化参数--><context-param><param-name>contextClass</param-name><param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value></context-param><context-param><!--定义参数的名称,必须是唯一的--><param-name>contextConfigLocation</param-name><!--定义参数的值--><param-value>at.guigu.config.SpringConfiguration</param-value></context-param><!--监听器--><!--配置Spring所提供的`ContextLoaderListener` 监听器--><listener><!--监听器类的全限定名--><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--声明一个Servlet--><servlet><!--声明的Servlet的类名--><servlet-name>UserServlet</servlet-name><!--声明的Servlet的全限定名--><servlet-class>at.guigu.web.UserServlet</servlet-class></servlet><!--将URL模式映射到特定的Servlet上(即UserServlet)--><servlet-mapping><!--指定的Servlet的类名--><servlet-name>UserServlet</servlet-name><!--给指定的Servlet设置url,相当于@WebServlet("/userServlet")--><url-pattern>/userServlet</url-pattern></servlet-mapping> </web-app>
银行账户间转账业务
-
Step1: 在dao包下的
UserDao
接口中写入查询方法,然后在对应的SQL映射文件中写入对应SQL语句注意:简单查询SQL语句采用注解方式,复杂SQL语句采用映射文件方式(具体操作可详见Spring集成MyBatis部分内容)
-
UserDao
接口代码如下package at.guigu.dao;import at.guigu.pojo.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update;import java.util.List;@Mapper public interface UserDao {@Update("update account set money = money + #{money} where name = #{name}")void inMoney(@Param("name") String name, @Param("money") Double money);@Update("update account set money = money - #{money} where name = #{name}")void outMoney(@Param("name") String name, @Param("money") Double money);@Select("select * from account")List<User> all(); }
注意:定义含多个参数的接口方法时,要通过
@Param("参数")
注解来将对应的方法参数放到SQL映射文件中SQL语句对应的参数占位符上,其中注解参数名称要和SQL映射文件中SQL语句中的参数占位符名称对应一致
-
-
Step2: 在业务层service包下的
UserService
类来调用dao
包下的UserDao
接口中的方法,代码如下- Step2-1: 创建transfer方法来完成账户转账操作,同时该方法要加上
@Transactional
注解来开启事务(注意:若业务层为接口和实现类,则将该注解添加到接口中对应的方法上,由于博主在业务层使用的直接是类,无接口,所以加在了类中的方法上)
package at.guigu.service;import at.guigu.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;@Service("userService") public class UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void transfer(String out, String in, double money) {userDao.outMoney(out, money);userDao.inMoney(in, money);} }
- Step2-1: 创建transfer方法来完成账户转账操作,同时该方法要加上
-
Step3: 在表现层web包下的BrandServlet类中来调用业务层service包中的UserService类中的方法,代码如下
package at.guigu.web;import at.guigu.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException;public class UserServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1 获取最大域ServletContext对象ServletContext servletContext = request.getServletContext();//等同于ServletContext servletContext = request.getSession().getServletContext();// 2 获取应用上下文对象(即IOC容器)ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);//3 获取beanUserService userService = app.getBean(UserService.class);// 等同于UserService userService = (UserService) app.getBean("userService");//4 调用方法执行SQL语句userService.transfer("张三", "李四", 500);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
-
Tomcat运行截图如下
-
无异常时,操作全部执行。张三、李四分别由原来的1000变为了1100、900,如图所示
-
此时若将业务层的代码中添加一个异常则操作就会全不执行,运行截图如下
-
-
注意
- Spring注解式事务(即
@Transactional
)通常添加在业务层接口中而不会添加到业务层实现类中,以此来降低耦合。 - 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务
- Spring注解式事务(即
Spring事务角色
- 事务管理员:是发起事务方,在Spring中指业务层开启事务的方法
- 事务协调员:是加入事务方,在Spring中指持久层的方法,也可以是业务层的方法
Spring事务相关配置
Spring配置文件形式
标签 | 解释 |
---|---|
<tx:advice id transaction-manager> | 配置事务通知(即声明式事务的增强)。id 为事务通知的唯一标识transaction-manager 属性值为事务管理器对应bean的唯一标识 |
<tx:advice> 的内嵌标签 | 解释 |
<tx:attributes> | 配置事务属性。代替了TransactionDefinition 接口的作用 |
<tx:attributes> 的内嵌标签 | 解释 |
<tx:method name propagation isolation read-only timeout rollback-for no-rollback-for> | 配置切入点方法的事务参数 |
<tx:method> 的属性 | 解释 |
name | 指定需要应用事务管理的方法的名称( 即切入点方法的名称 ),可以使用通配符。* 代表匹配任意字符;.. 代表匹配任意数量的参数。例如:save* 匹配所有以 “save” 开头的方法;*Service.*(..) 匹配所有 *Service 类中接受任意参数的方 |
propagation | 控制事务的传播行为,即当前事务如何与外部事务交互。传播行为共有7种,分别为:REQUIRED (默认)、REQUIRES_NEW 、SUPPORTS 、NOT_SUPPORTED 、MANDATORY 、NEVER 、NESTED |
isolation | 定义事务的隔离级别,控制不同事务之间如何隔离。共有四种:READ_UNCOMMITTED 、READ_COMMITTED 、REPEATABLE_READ 、SERIALIZABLE 。MySQL默认为REPEATABLE_READ ;Oracle默认为READ_COMMITTED |
read-only | 事务是否为只读事务,若是则返回true ,默认为false 。建议查询时设置为true |
timeout | 事务的超时时间(单位:秒),如果事务在指定时间内没有完成,Spring 会自动回滚事务。默认没有超时限制。 |
rollback-for | 指定哪些异常应该触发事务回滚。可以指定多个异常类,多个异常类之间使用逗号分隔。比如:rollback-for="SQLException,IOException" |
no-rollback-for | 与 rollback-for 相对,指定哪些异常不会触发事务回滚 |
Spring注解形式
注解 | 解释 |
---|---|
@Mapper | 用在持久层接口上,代表由Spring自动生成持久层接口对应的bean |
@MapperScan("at.guigu.dao") | 代理@Mapper 注解,用在Spring核心配置类上。代表由Spring自动生成持久层接口对应的bean |
@EnableTransactionManagement | 用于在Spring核心配置类中启用 Spring 事务管理的功能,开启对声明式事务管理支持。 |
@Transactional(propagation isolation timeout readOnly rollbackFor noRollbackFor value) | 用在接口或方法上,当用在接口上时代表该接口中的所有方法均开启事务;当用在方法上代表该方法开启事务;该注解可配置事务属性。注意:该注解一般加在业务层接口中而不会添加到其实现类上,以此来降低耦合。 |
@Transactional 注解属性 | 解释 |
propagation | 控制事务的传播行为,即当前事务如何与外部事务交互。传播行为共有7种,分别为:REQUIRED (默认)、REQUIRES_NEW 、SUPPORTS 、NOT_SUPPORTED 、MANDATORY 、NEVER 、NESTED |
isolation | 定义事务的隔离级别,控制不同事务之间如何隔离。共有四种:READ_UNCOMMITTED 、READ_COMMITTED 、REPEATABLE_READ 、SERIALIZABLE 。MySQL默认为REPEATABLE_READ ;Oracle默认为READ_COMMITTED |
timeout | 事务的超时时间(单位:秒),如果事务在指定时间内没有完成,Spring 会自动回滚事务。默认没有超时限制。 |
readOnly | 事务是否为只读事务,若是则返回true ,默认为false 。建议查询时设置为true |
rollbackFor | 指定哪些异常应该触发事务回滚。可以指定多个异常类,多个异常类之间使用逗号分隔。比如:rollbackFor = {Exception1.class, Exception2.class} |
noRollbackFor | 与 rollback-for 相对,指定哪些异常不会触发事务回滚 |
-
注意
-
使用Spring核心配置文件和配置类结合的方式时:若不在Spring的核心配置类中加上
@EnableTransactionManagement
注解则需要在Spring的核心配置文件中加上如下代码来启用 Spring 事务管理的功能,开启对声明式事务管理支持。`<tx:annotation-driven transaction-manager="transactionManager"/>`
-
当
@Transactional
注解在接口或类上使用时,该接口或类中的所有方法均开启事务;若该注解不仅在接口或类上使用,还在该接口或类中的方法上使用时,则事务属性的配置以使用在方法上的@Transactional
注解为准
-
相关文章:

Spring完整知识三(完结)
Spring集成MyBatis 注意 Spring注解形式集成MyBatis时,若SQL语句比较复杂则仍采用映射文件形式书写SQL语句;反之则用注解形式书写SQL语句,具体可详见Spring注解形式 环境准备相同步骤 Step1: 导入相关坐标,完整pom.…...

保姆级教程Docker部署Redis镜像
目录 1、创建挂载目录和配置文件 2、运行Redis镜像 3、查看redis运行状态 1、创建挂载目录和配置文件 # 创建宿主机Redis配置文件存放目录 sudo mkdir -p /data/docker/redis/conf# 创建Redis配置文件 cd /data/docker/redis/conf sudo touch redis.conf 到Github上找到Redi…...

子类有多个父类的情况下Super不支持指定父类来调用方法
1、Super使用方法 super()函数在Python中用于调用父类的方法。它返回一个代理对象,可以通过该对象调用父类的方法。 要使用super()方法,需要在子类的方法中调用super(),并指定子类本身以及方法的名称。这样就可以在子类中调用父类的方法。 …...

AI大模型ollama结合Open-webui
AI大模型Ollama结合Open-webui 作者:行癫(盗版必究) 一:认识 Ollama 1.什么是Ollama Ollama是一个开源的 LLM(大型语言模型)服务工具,用于简化在本地运行大语言模型,降低使用大语言模型的门槛,使得大模型的开发者、研究人员和爱好者能够在本地环境快速实验、管理和…...

RK3568笔记2:NOR_Flash和NAND_Flash与SDMMC和eMMC
1. 本质区别 特性NOR Flash/NAND FlashSDMMC/eMMC定义基础存储器(原始闪存芯片)基于闪存芯片的存储模块,带有控制器组成结构只有原始存储芯片存储芯片 控制器控制方式需主机直接控制,读写逻辑由主机完成内置控制器,主…...

windows python qt5 QChartView画折线图
环境:windows pyqt5 ,用QCartView画折线图 环境需要提前安装 pip install PyQtChart 折线图随着时间推移会不断移动,主动更新x轴坐标 import sys from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout from PyQt5.QtChart imp…...

阿里云通义千问:全面解析智能云服务先锋
一、技术架构与基础 模型构建基石 采用大规模语料库训练,涵盖多领域知识,如科学、历史、文学等,确保知识储备丰富多样。运用先进的神经网络架构,深度优化模型结构,提高信息处理效率与准确性。持续的语料更新机制&…...

QT 贪吃蛇
1.注意点 新new对象时,要food->show(),否则屏幕不显示 setText() 要求字符串 事件的触发必须写在QWidget中或这是他的子类才能触发,snake.cpp继承的是QTimer 产生动态的原因是定时器每间隔一秒执行一次 信号可以定义在别的.cpp中,只要连接…...

二、点亮希望之光:寄存器与库函数驱动 LED 灯
文章目录 一、寄存器1、存储器映射2、存储器映射表3、寄存器4、寄存器映射5、寄存器重映射6、总线基地址、外设基地址、外设寄存器地址7、操作寄存器(以操作一个GPIO口为例)1. 寄存器地址定义部分2. GPIOD_Configuration 函数部分3. main 函数部分 二、库…...

Oracle 用户管理模式下的恢复案例-不完全恢复
1. 不完全恢复的几种常用方法 01. recover database using backup controlfile 如果丢失当前控制文件,用冷备份的控制文件恢复的时候,用来告诉 oracle,不要以 controlfile 中的 scn 作为恢复的终点; 02. recover database until …...

SharpDevelop IDE IViewContent.cs类
文件位置:IViewContent.cs /// <summary>/// IViewContent is the base interface for "windows" in the document area of SharpDevelop./// A view content is a view onto multiple files, or other content that opens like a document/// (e.…...

Unity RectTransUtility工具类
这个工具主要是用于动态生成UI的情况。项目中我们通过配置UI的锚点、位置以及大小(位置、大小都是通过蓝湖看到的),然后通过代码动态生成UI。 大部分情况下只要合理设置锚点,那么生成出来的UI就已经满足了适配的要求。 using UnityEngine;public static…...

React性能优化
三个可以优化的地方 避免过度多次渲染 组件会在以下情况下重新渲染 注意:例如组件组合的形式,<Test><Counter></Counter></Test>,即使Test发生了重新渲染,Counter也不会重新渲染。另外使用React这样的库或框架时&a…...

前端开发流程实操:从概念到上线
在前端开发这个充满创意与技术挑战的领域,一个清晰的开发流程是确保项目顺利进行并达到预期效果的关键。 下面就和大家分享一下前端开发的实操流程。 一、项目启动与需求分析 前端开发不是孤立的,它是整个项目的一部分,所以首先要与项目团…...

Metasploit使用
最近在学Metasploit,Metasploit是一个免费的、可下载的渗透测试框架,通过它可以很容易地获取、开发并对计算机软件漏洞实施攻击,是一个集成了渗透测试全流程的渗透工具。 图一 模块:模块组织按照不同的用途分为7种类型的模块 &am…...

Milvus向量数据库05-常见问题整理
Milvus向量数据库05-常见问题整理 1-什么是PipeLine 这张图展示了一个文档处理和搜索系统的架构,主要分为两个部分:Ingestion Pipeline(摄取管道)和 Search Pipeline(搜索管道)。下面是对图中各部分的详细…...

Ruby On Rails 笔记3——表的增删改查
1.Migration Migrations是一种便利的方法,能以重现的方式随时间推移改变数据库schema. 使用Ruby Domain Specific Language (DSL),因此你不用手写SQL,进而使你的schema和changes与数据库独立。 可以把每次migration看作是数据库的一个新“版本”。A schema开始时什么都没有…...

CSS3 动画详解,介绍、实现与应用场景详解
CSS3 动画概述 CSS3 动画是通过 CSS3 的新特性来实现元素的动态变化。与传统的 JavaScript 动画不同,CSS3 动画主要通过 CSS 属性的变化来实现动画效果,具有高效、轻量和易于实现的优点。CSS3 动画通常用于网页的动态交互效果、过渡效果、元素移动、缩放、旋转等场景。 一、…...

Winston-MySQL 使用文档
目录 简介 安装 配置 环境变量配置 日志级别和表配置 创建 Logger 实例 文件传输配置 控制台输出配置 完整代码 使用方法 记录信息日志 记录错误日志 记录警告日志 总结 简介 winston-mysql 是一个为 winston3.x 日志库设计的 MySQL 传输插件,允许你…...

java日期工具: 获取两个时间段的时间段值,Java获得两个日期之间的所有年、月份、日。
文章目录 日期字符串格式化获取两个日期之间的所有日期 (字符串格式)获取两个时间段的时间段值,Java获得两个日期之间的所有年、月份、日。生效时间需要大于当前时间结束时间的月份不能大于当前月份日期字符串格式化 /*** 日期字符串格式化** @param time* @param Format_int…...

【Rive】混合动画
1 混合动画简介 【Rive】动画 中介绍了 Rive 中动画的基础概念和一般动画的制作流程,本文将介绍混合动画的基础概念和一般制作流程。Unity 中混合动画介绍详见→ 【Unity3D】动画混合。 混合动画是指同一时刻多个动画按照一定比例同时执行,这些动画控制的…...

qt应用程序崩溃日志和转储dmp文件对于定位问题
qt应用程序崩溃日志和转储文件对于定位问题 一. DMP 文件包含的信息:二. 分析 DMP 文件的主要方法:三. 生成更详细的 DMP 文件:四. 分析 DMP 文件的注意事项:五. 实用建议:六. 实战 一. DMP 文件包含的信息:…...

Mysql架构
连接层 最上层是一些客户端和连接服务,负责客户端的连接,验证账号密码等授权认证 服务层 主要完成大多数的核心服务功能,对sql进行解析,优化,调用函数,如果是查询操作,有没有缓存等操作操作。所…...

杂发单的单据类型一个参数的逻辑
【核准中可改】被产线滥用了。它们可以这样做,开立一张杂发单,打印出来交领导层签名。单据要交财务做核算的。然后去修改杂发单的材料。以为可以瞒天过海。2个仓库,一个中掉坑里,一个发现了它们的拙劣的手段,上报之后没…...

Linux系统 vim 编辑文件搜索关键字用法
1、首先确保在normal模式下,按ESC后不在insert模式 输入 /test或?test 此时就会匹配 test 字符串,并且高亮显示 2、向前搜索 /字符串:按n匹配下一个目标,按N匹配上一个目标 3、向后搜索 ?字符串:按n匹配上一个目标…...

Vue智慧商城项目
创建项目 vue组件库 — vant-ui(常用于移动端) Vant 2 - 轻量、可靠的移动端组件库 安装vant npm i vantlatest-v2 -S 引入组件 按需导入和全部导入 全部导入 整个组件库的所有组件都导进来,缺点是增加了代码包体积 main.js import…...

Qt Window应用程序去掉控制台窗口
Qt Window应用程序去掉控制台窗口 方式一 set(PROJECT_SOURCESWIN32main.cppmainwindow.hpp )add_executable(Tool-V2${PROJECT_SOURCES} )方式二 set_target_properties(Tool-V2 PROPERTIESMACOSX_BUNDLE TRUEWIN32_EXECUTABLE TRUE )参考文献: cmake Qt 项目…...

软件测试最新项目合集【商城、外卖、银行、金融等等.......】
项目一:ShopNC商城 项目概况: ShopNC商城是一个电子商务B2C电商平台系统,功能强大,安全便捷。适合企业及个人快速构建个性化网上商城。 包含PCIOS客户端Adroid客户端微商城,系统PC后台是基于ThinkPHP MVC构架开发的跨…...

SAP SD学习笔记18 - 投诉处理4 - 请求书订正依赖,投诉处理流程的总结
上一章讲了 Credit/Debit Memo依赖,Credit/Debit Memo。Credit Memo依赖 本质上是一张受注票;Credit Memo 本质上是一张请求票。 SAP SD学习笔记17 - 投诉处理3 - Credit/Debit Memo依赖,Credit/Debit Memo-CSDN博客 本章继续讲本图中的内容…...

VBA批量提取PDF内容的程序
VBA批量提取PDF内容的程序 Sub ExtractPDFText()Dim pdfApp As Acrobat.AcroAppDim pdfDoc As Acrobat.CAcroPDDocDim pdfPage As Acrobat.AcroPDPageDim txtData As StringDim i As IntegerDim filePath As StringDim outputFolder As StringDim outputFileName As String 初…...