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.…...
kafka-clients之ConsumerConfig
Kafka ConsumerConfig 中的配置项用于定义消费者的行为,如消费方式、偏移管理、组协调等。以下是ConsumerConfig中的关键配置项及其详细说明: 1. bootstrap.servers 类型:List<String>说明:Kafka集群的地址列表࿰…...
关于“浔川AI翻译”使用情况的调研报告
关于“浔川 AI 翻译”使用情况的调研报告 随着全球化进程加速及外语学习需求攀升,AI 翻译工具愈发普及。“浔川 AI 翻译”作为行业产品之一,为了解其市场表现与用户反馈,特开展本次问卷调查,现将关键结果汇报如下。 一、样本概…...
第一节、电路连接【51单片机-TB6600驱动器-步进电机教程】
摘要:本节介绍如何搭建一个51单片机TB6600驱动器步进电机控制电路,所用材料均为常见的模块,简单高效的方式搭建起硬件环境 一、硬件清单 ①51单片机最小控制系统 ②USB转TTL模块 ③开关电源 ④TB6600步进电机驱动器 ⑤二相四线步进电机 ⑥电…...
编程之路,从0开始:补充篇
Hello大家好!很高兴和大家又见面啦!给生活添点passion,开始今天的编程之路! 我的博客:<但凡. 我的专栏:《编程之路》、《题海拾贝》、《数据结构与算法之美》 欢迎点赞,关注! 这篇…...
<script src = “https://cdn.jsdelivr.net/npm/vue/dist/vue.js“></script>
这行代码是HTML中的一个<script>标签,用于在网页中嵌入Vue.js库。具体来说: <script>:是HTML中的一个标签,用于定义客户端的脚本,比如JavaScript。 src:是<script>标签的一个属性&#…...
RabbitMQ延时队列
RabbitMQ延时队列 什么是延时队列 延时队列顾名思义,即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。 应用场景 场景一:在订单系统中,一个用户下单之后通常有30分钟的时间进行支付,如…...
Java——异常机制(下)
1 异常处理之(捕获异常) (一般处理运行时异常) (try-catch-finally子句) (finally一般用于文件最后关闭) (catch捕获的子类在前父类在后——>不然父类在前面都让父类捕获掉了,会报错) (Exception是父类放在最后,如果前面没有捕获到,就…...
9. 一分钟读懂“策略模式”
9.1 模式介绍 策略模式是一种行为型设计模式,用于在运行时灵活切换对象的行为或算法,它将算法封装为独立的类,使得它们可以互相替换,而不会影响使用这些算法的客户端代码。 策略模式的核心思想是:定义一系列可互换的算…...
python调用GPT-4o实时音频 Azure OpenAI GPT-4o Audio and /realtime
发现这块网上信息很少,记录一下 微软azure入口 https://learn.microsoft.com/zh-cn/azure/ai-services/openai/realtime-audio-quickstart?pivotsprogramming-language-ai-studio sdk文档 https://github.com/azure-samples/aoai-realtime-audio-sdk?tabread…...
linux查看应用程序执行时间
命令 ps -eo pid,comm,lstart,etime,args | grep -i java ps:是查看进程状态的命令。-eo:是-e和-o参数的组合,-e表示显示所有进程,-o后面跟的是自定义输出格式。pid:表示进程ID。comm:表示命令名&#x…...
mybatis之数据统计与自定义异常处理
文章目录 需求描述定义实体方式一、mybatisPlus实现方式二、自定义SQL实现简单查询过滤查询 异常处理1、SQL拼写异常 在使用Mybatis或MybatisPlus进行数据统计,在【 SpringBoot的Mybatis-plus实战之基础知识】中对mybatisplus引入有介绍,本次要使用其进…...
Jenkins 中自定义Build History中显示构建信息
有时候会遇到一个代码仓库下面会有多个不同的分支,而这写分支表示着不同的开发者在开发新的需求,但是这样就会出现一个问题,在Jenkins上进行多分支构建的时候,很难找到哪一个是属于自己分支构建的,这样的问题大家应该都…...
安装certbot(ubuntu系统)
安装nginx 更新软件包列表 sudo apt update 更新软件包列表 sudo apt install nginx 更新软件包列表 sudo systemctl status nginx 注意:强烈推荐使用,系统直接安装nginx,(不推荐使用docker安装nginx)为后续更简单…...
QTextBrowser按照段落来显示html的文字
在 PyQt5 中,QTextBrowser 是一个用来显示富文本(如 HTML)内容的小部件。要让 QTextBrowser 按照段落来显示 HTML 内容,可以确保 HTML 中的每个段落被正确地封装在 <p> 标签中或其他合适的 HTML 标签中。 基本思路 HTML 格…...
如何让Google快速收录你的页面?
要让Google更快地收录你的网站内容,首先需要理解“爬虫”这个概念。Google的爬虫是帮助它发现和评估网站内容质量的工具,如果你的页面质量高且更新频率稳定,那么Google爬虫更可能频繁光顾。通常情况下,通过Google Search Console&…...
用Linux完成firewall-cmd配置防火墙
注意这里是openeuler的系统 假设在内网架设了一台Web服务器,IP地址是192.168.1.2,端口是80,设置内网网段192.168.1.0/24中的主机均可以访问此Web服务器,如图所示 步骤如下(包含结果) 这里再服务端(192.168.131.10)上配置&#x…...
关于光速的猜想
光速实际也可能是爱因斯坦制造出来的一个量,就像质量(重力mg,有这个关系在,m起的名字后来就演变成了质量)是牛顿造出的一个量,一个常量,刚开始都叫c,只是习以为常后起了个名字罢了&a…...
Mybatis-Flex的简单入门,Mybatis-Flex和其它框架对比
前言 最近做项目,项目组必须要用Mybatis-Flex,然后自己去学了一下,给大家进行分享。 MyBatis-Flex 是 MyBatis 的一个增强工具,旨在简化 MyBatis 的使用并提高开发效率。它通过提供一系列的便捷方法和特性来减少重复代码的编写&am…...
轻量化特征融合 | YOLOv11 引入一种基于增强层间特征相关性的轻量级特征融合网络 | 北理工新作
本改进已同步到Magic框架 摘要—无人机图像中的小目标检测由于分辨率低和背景融合等因素具有挑战性,导致特征信息有限。多尺度特征融合可以通过捕获不同尺度的信息来增强检测,但传统策略效果不佳。简单的连接或加法操作无法充分利用多尺度融合的优势,导致特征之间的相关性不…...
linux的磁盘管理
认识磁盘 从外到内,磁道从0~XXX。扇区默认512字节先读完一个柱面的0号磁道,接着读第二个柱面的0号磁道……直至所有柱面的0号磁道读写完毕,进入下一个磁道。 磁盘命名规则 物理磁盘:sd开头;虚拟磁盘vd开头 sdb1:第二…...
Alibaba EasyExcel 导入导出全家桶
一、阿里巴巴EasyExcel的优势 首先说下EasyExcel相对 Apache poi的优势: EasyExcel也是阿里研发在poi基础上做了封装,改进产物。它替开发者做了注解列表解析,表格填充等一系列代码编写工作,并将此抽象成通用和可扩展的框架。相对p…...
使用Scala编写一个简单的决策树分类测试demo
使用Scala编写一个简单的决策树分类测试demo,我们可以基于Apache Spark的MLlib库来实现。这里将展示如何创建一个简单的决策树分类器,并用它来进行预测。我们将遵循以下步骤: 设置环境和导入必要的包。加载并准备数据集。构建决策树模型。使…...
SQLServer中使用ISNULL替换为指定的替换值
ISNULL (Transact-SQL) 适用于: SQL ServerAzure SQL 数据库Azure SQL 托管实例Azure Synapse Analytics 分析平台系统 (PDW)Microsoft Fabric 中的 SQL 分析端点Microsoft Fabric 中的仓库 NULL替换为指定的替换值。 1、语法 ISNULL ( check_expression , rep…...
69 mysql 中 is null 的实现
前言 Mysql 中我们偶尔会用到 字段为 NULL 的情况 这时候 我们只能使用查询 “select * from tz_test_02 where field1 is null;” 来进行 field1 字段为 null 的行的查询 然后如果是使用 “select * from tz_test_02 where field1 null;” 你会发现查询 不出数据 但是如…...
【信息系统项目管理师】第9章:项目范围管理-基础和过程 考点梳理
文章目录 9.1 管理基础9.1.1 产品范围和项目范围9.1.2 管理新实践 9.2 项目范围管理过程9.2.1 过程概述9.2.2 裁剪考虑因素9.2.3 敏捷与适应方法 【学习建议】本章节内容属于10大管理知识领域,选择、案例、论文都会考,建议重点学习。项目范围管理包括确保…...
智能制造标准体系建设指南
一、智能制造系统架构总览 智能制造作为当今制造业转型升级的核心,深度整合了新一代信息技术与传统制造工艺,催生出一个横跨产品全生命周期、纵贯多层级组织架构,并彰显多元智能特性的复杂系统。这一架构从生命周期、系统层级、智能特征三个…...
怎么获取键值对的键的数值?
问: 通过paelData.cardMap.C0002112可以获取到Cooo2112里面的数据,但是有时候接口返回的不是C0002112而是C0002093或者其他值,请问我该怎么写? 后端返回的数据是这样的: cardMap: { C0002112: { name: Item 1, va…...
反向代理后Request.Url.AbsoluteUri获取成了内网IP
出现的问题:用户请求的是域名,而后端通过Request.Url.AbsoluteUri获取用户请求的绝对路径时,变成了内网IP 解决方式: 反向代理配置中加上: proxy_set_header Host $host; # proxy_set_header Host h o s t : host: h…...
NLP论文速读(斯坦福大学)|使用Tree将语法隐藏到Transformer语言模型中正则化
论文速读|Sneaking Syntax into Transformer Language Models with Tree Regularization 论文信息: 简介: 本文的背景是基于人类语言理解的组合性特征,即语言处理本质上是层次化的:语法规则将词级别的意义组合成更大的成分的意义&…...
OpenCV-图像阈值
简单阈值法 此方法是直截了当的。如果像素值大于阈值,则会被赋为一个值(可能为白色),否则会赋为另一个值(可能为黑色)。使用的函数是 cv.threshold。第一个参数是源图像,它应该是灰度图像。第二…...
window系统,照片应用打开图片,但是提示操作系统找不到已输入的环境选项,请问怎么解决,以便能打开图片
文章目录 问题描述问题解决一、先用AI提问,看能否得到解答二、最终的解决方法-修改环境变量 至此问题解决。 问题描述 Windows中,使用默认的图片应用打开图片时,提示: 操作系统找不到已输入的环境选项如下图: 这个问…...
Excel之查找函数-XLOOKUP
背景: 某些数据处理,需要对比两个乱序或者数据不完全相同的数值,取到另外一个数据值,数据量大的情况下,人工对比太耗时,XLOOKUP函数是一个查找函数,可以通过遍历对比某一单元格的值,…...
深入理解 Java 内存管理:堆和栈
深入理解 Java 内存管理:堆和栈的全面解析 在 Java 编程语言中,内存管理是一个至关重要的概念,其中堆(Heap)和栈(Stack)是两个核心的内存区域。理解它们的工作原理、用途以及它们在程序执行过程…...
深度全解析开放开源大模型之BLOOM
BLOOM是 BigScience Large Open-science Open-access Mul-tilingual Language Model首字母的缩写。 BigScience 不是财团(consortium),也不是正式成立的实体。这是一个由HuggingFace、GENCI和IDRIS发起的开放式协作组织,以及一个…...
下载谷歌浏览器的官方离线安装包
网址:https://support.google.com/chrome/answer/95346?hlzh-Hans&coGENIE.Platform%3DDesktop#zippy%2Cwindows...
ORACLE创建用户报错ORA-65096: invalid common user or role name
在高版本的oracle中创建用户时提示错误ORA-65096: invalid common user or role name,官网说明用户名必须使用C##或c##开头。以下方法亲测有效。 通过设置"_ORACLE_SCRIPT"参数为true来临时绕过CDB中创建用户必须以"C##"开头的限制。请注意&…...
河工oj第七周补题题解2024
A.GO LecturesⅠ—— Victory GO LecturesⅠ—— Victory - 问题 - 软件学院OJ 代码 统计 #include<bits/stdc.h> using namespace std;double b, w;int main() {for(int i 1; i < 19; i ) {for(int j 1; j < 19; j ) {char ch; cin >> ch;if(ch B) b …...
运维大屏与设备仪表盘:打造高效运维管理的视觉中枢
在快速发展的信息化时代,运维行业面临着前所未有的挑战。随着业务规模的不断扩大和系统复杂度的日益增加,如何高效、准确地监控和管理设备运行状态,成为运维团队亟待解决的问题。运维大屏与设备仪表盘作为运维管理的重要工具,为运…...
计算机视觉与医学的结合:推动医学领域研究的新机遇
目录 引言医学领域面临的发文难题计算机视觉与医学的结合:发展趋势计算机视觉结合医学的研究方向高区位参考文章结语 引言 计算机视觉(Computer Vision, CV)技术作为人工智能的重要分支,已经在多个领域取得了显著的应用成果&…...
使用setsockopt函数SO_BINDTODEVICE异常,Protocol not available
前言 最近在使用OLT的DHCP Server的时候发现一些异常现象,就是ONU发的一个vlan的discover包其他不同vlan的DHCP地址池也会收到,导致其他服务器也发了offer包,ONU同时会有多个ip地址。一开始是没有使用SO_BINDTODEVICE,后面查到使…...
rpm包转deb包或deb包转rpm包
Debian系(Ubuntu、Deepin、麒麟Destop等)用的安装包是deb的,Red Hat系(CentOS、欧拉、麒麟Server等)用的安装包是rpm的。 如果需要在Ubuntu上安装rpm,或需要在CentOS上安装deb,需要安装alien s…...
ChatGPT 和文心一言哪个更好用?
ChatGPT vs 文心一言:哪个更好用? 引言 在人工智能蓬勃发展的今天,聊天机器人已经成为我们生活和工作中不可或缺的一部分。你可能听说过ChatGPT和文心一言这两个热门的聊天机器人,它们分别来自OpenAI和百度。那么,究…...
T113-S3 Tina 存储类型修改
前面介绍了如何在 Tina 中添加新的板子,本节介绍如何修改板子存储类型。 1、确定存储类型 Tina 支持多种存储类型,包括 SD 卡、eMMC、SPI NAND、SPI NOR 等。在添加板子之前,需要确定板子使用的存储类型。 存储类型修改 在 device/config/…...
【css】基础(一)
本专栏内容为:前端专栏 记录学习前端,分为若干个子专栏,html js css vue等 💓博主csdn个人主页:小小unicorn ⏩专栏分类:css专栏 🚚代码仓库:小小unicorn的代码仓库🚚 &a…...
Linux中inode
磁盘的空间管理 如何对磁盘空间进行管理? 假设在一块大小为500G的磁盘中,500*1024*1024524288000KB。在磁盘中,扇区是磁盘的基本单位(一般大小为512byte),而文件系统访问磁盘的基本单位是4KB,因…...
verilog fpga 如果if语句==号后面是个表达式 运行不稳定
来自 文心一言 在Verilog中编写FPGA代码时,使用if语句进行条件判断是常见的操作。然而,当if语句中的条件判断使用的是表达式(如后面是一个复杂的表达式),确实可能会遇到运行不稳定的问题。这通常是由于以下几个原因导…...
BFS广度优先搜索
广度优先搜索(Breadth-First Search, BFS)是一种用于遍历或搜索树或图的算法。 它从根节点开始,逐层访问每个节点,并在访问完一层后才访问下一层。BFS常用于寻找最短路径的问题。 下面将用实例一和实例二来实现BFS广度优先搜索 …...
uniapp 自定义导航栏增加首页按钮,仿微信小程序操作胶囊
实现效果如图 抽成组件navbar.vue,放入分包 <template><view class"header-nav-box":style"{height:Props.imgShow?:statusBarHeightpx,background:Props.imgShow?:Props.bgColor||#ffffff;}"><!-- 是否使用图片背景 false…...
小程序项目的基本组成结构
分类介绍 项目根目录下的文件及文件夹 pages文件夹 用来存放所有小程序的页面,其中每个页面都由4个基本文件组成,它们分别是: .js文件:页面的脚本文件,用于存放页面的数据、事件处理函数等 .json文件:…...