目录
什么是Spring
Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架。,其目的是简化企业的应用程序开发,降低侵入性,Spring提供的IOC和AOP功能,可以将组件之间的耦合度降到最低,便于后期的维护和升级,实现了软件的高内聚低耦合思想。
优点:
- Spring能帮我们根据配置文件创建及组装对象之间的依赖关系。帮助我们管理Bean对象的生命周期,以及维护各个Bean对象之间的关系。
- Spring是一种非侵入式(non-invasive)框架,它可以使应用程序代码对框架的依赖最小化。
- 支持声明式事务处理,只需要通过配置就可以完成对事务的管理,而无须手动编程。.
- Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts、Hibernate、MyBatis、Quartz等)的直接支持。
Spring IoC入门实例
IoC(控制反转):对象的实例不再由调用者来创建,而是由 Spring容器来创建。Spring容器会负责控制程序之间的关系,而不是由调用者的程序代码直接控制。这样,控制权由调用者转移到Spring容器,控制权发生了反转,这就是Spring的控制反转。
IoC配置文件实例
创建一个maven项目
导入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.4</version>
</dependency>
<!-- 单元测试依赖-->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
<scope>provided</scope>
</dependency>
</dependencies>
spring-webmvc帮我们导人了许多核心依赖(aop,deans, context,core等)因此导入这一个依赖就够了。junit用于测试,lombok偷懒用的可有可无。
创建普通的Java类
Preson类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Preson {
private Book book;
private String[] hobby;
private List<String> list;
private Map<String, String> map;
private Properties properties;
}
Book类
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Book {
private int bookID;
private String bookName;
private int bookCounts;
private String detail;
}
beans.xml
<?xml version="1.0" encoding="UTF8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 通过构造方法注入属性值,不需要提供setXXX()方法,-->
<bean id="book" class="com.jie.pojo.Book">
<!-- 通过构造器参数的顺序注入(不推荐使用,当有多个构造器时,默认使用第一个符合的构造器,如果要使用其他的构造器可以通过type类型匹配来区分不同的构造方法)-->
<constructor-arg value="1"/>
<!-- 通过参数名注入(推荐使用,无歧义一眼就可以看到是给谁赋值)-->
<constructor-arg name="bookCounts" value="10"/>
<!-- 通过指定参数顺序名注入(不推荐使用)-->
<constructor-arg index="1" value="java"/>
<constructor-arg name="detail" value="咖啡"/>
<!-- 通过set方法注入,要提供XXset方法-->
<!-- 复杂类型注入-->
</bean>
<bean id="preson" class="com.jie.pojo.Preson">
<!-- 类类型:ref属性指向对book的引用(id为book的bean)-->
<property name="book" ref="book"/>
<!-- 数组类型-->
<property name="hobby">
<array>
<value>打篮球</value>
<value>打网球</value>
</array>
</property>
<!-- list集合-->
<property name="list">
<list>
<value>睡觉</value>
<value>玩游戏</value>
</list>
</property>
<!-- map集合-->
<property name="map">
<map>
<entry key="今晚" value="吃鸡"/>
<entry key="王者" value="五杀"/>
</map>
</property>
<!-- properties-->
<property name="properties">
<props>
<prop key="name">唐三</prop>
<prop key="sex">男</prop>
</props>
</property>
</bean>
</beans>
注入方式有两种:构造器注入和set方法注入 。
通过构造方法注入属性值,不需要提供setXXX()方法。
通过set方法注入,要提供对应的XXset方法
applicationContext.xml
<?xml version="1.0" encoding="UTF8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="classpath:beans.xml"/>
</beans>
import导入其他xml文件。
测试
@Test
public void test() {
// 1. 加载Spring配置文件,根据创建对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 得到bean对象
Preson preson = context.getBean("preson", Preson.class);
System.out.println(preson.toString());
}
运行结果
Preson(book=Book(bookID=1, bookName=java, bookCounts=10, detail=咖啡), hobby=[打篮球, 打网球], list=[睡觉, 玩游戏], map={今晚=吃鸡, 王者=五杀}, properties={name=唐三, sex=男})
xml配置文件和注解混合使用实例
修改beans.xml
<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:contet="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描包中扫描类、方法、属性上是否有注解 -->
<contet:component-scan base-package="com.jie.pojo"/>
<bean id="preson" class="com.jie.pojo.Preson">
<!-- 类类型:ref属性指向对book的引用(id为book的bean)-->
<property name="book" ref="book"/>
<!-- 数组类型-->
<property name="hobby">
<array>
<value>打篮球</value>
<value>打网球</value>
</array>
</property>
<!-- list集合-->
<property name="list">
<list>
<value>睡觉</value>
<value>玩游戏</value>
</list>
</property>
<!-- map集合-->
<property name="map">
<map>
<entry key="今晚" value="吃鸡"/>
<entry key="王者" value="五杀"/>
</map>
</property>
<!-- properties-->
<property name="properties">
<props>
<prop key="name">唐三</prop>
<prop key="sex">男</prop>
</props>
</property>
</bean>
</beans>
<contet:component-scan base-package=“com.jie.pojo”/>开启注解扫描,扫描com.jie.pojo包及其子包中的注解,如果某个类的头上带有特定的注解@Component,@Repository,@Service,@Controller,就会将这个对象作为Bean注册进Spring容器。不开启注解扫描注解不会生效
修改Book类
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
public class Book {
@Value("1")
private int bookID;
@Value("java")
private String bookName;
@Value("8")
private int bookCounts;
@Value("咖啡")
private String detail;
}
其他不变,
通常对于基本数据类型、String 使用@Value()注解注入,对于类类型使用 @Autowired或@Resource注入,对于集合等复杂类型,使用xml配置文件
spring aop 入门实例
aop(面向切面编程) :spring aop 通过动态代理,将那些与业务无关,却在业务模块的许多方法中需要用到的代码(切面类的方法)。在方法调用时,动态的插入这些代码。
可以让我门专注自己本身的业务,而不去想一些其他与业务无关的事情,这些其他的事情包括:安全,事务,日志等。
使用配置文件实现aop
要使用spring aop除了上面的依赖还需要导入aspectjweaver依赖。
<dependency>
<groupId> org.aspectj</groupId >
<artifactId> aspectjweaver</artifactId >
<version> 1.8.9</version>
</dependency>
不导入会报错如下错误
MyTransaction(切面类)
@Component
public class MyTransaction {
public void beginTransaction(){
System.out.println("开启事务 ");
}
public void commit(){
System.out.println("提交事务");
}
}
Preson类中添加可以被通知插入的方法
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Preson {
@Autowired
private Book book;
private String[] hobby;
private List<String> list;
private Map<String, String> map;
private Properties properties;
public void aopTest(){
System.out.println("事务正在处理");
}
}
aop.xml配置
<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 切面类 -->
<bean id="myTransaction" class="com.jie.pojo.MyTransaction"></bean>
<!--aop配置-->
<aop:config>
<!-- 切入点 声明那些地方可以被插入通知-->
<aop:pointcut id="tx" expression="execution(* com.jie.pojo. Preson.*(..))"/>
<!-- 切面类 -->
<aop:aspect ref="myTransaction">
<!-- 设置切面类的方法是什么通知(前置,后置,环绕等等), 并设置这些通知可以从那些切入点插入。-->
<aop:before method="beginTransaction" pointcut-ref="tx"/>
<aop:after method="commit" pointcut-ref="tx"/>
</aop:aspect>
</aop:config>
</beans>
切入点表达式execution(* com.jie.pojo.Preson.*(…))表示 Preson类的所有方法都是切入点。
applicationContext.xml中导入aop.xml
<?xml version="1.0" encoding="UTF8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="classpath:beans.xml"/>
<import resource="classpath:aop.xml"/>
</beans>
测试
@Test
public void test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Preson preson = context.getBean("preson", Preson.class);
preson.aopTest();
}
测试结果
使用注解实现aop
首先在applicationContext.xml中开启aop代理模式
<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<import resource="classpath:beans.xml"/>
<!-- 开启aop代理模式-->
<aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>
</beans>
不开启会使aop注解失效
修改MyTransaction类
@Component
@Aspect//标注这是一个切面类
public class MyTransaction {
//前置通知,括号里填切入点
@Before("execution(* com.jie.pojo.Preson.*(..))")
public void beginTransaction(){
System.out.println("开启事务 ");
}
//后置通知
@After("execution(* com.jie.pojo.Preson.*(..))")
public void commit(){
System.out.println("提交事务");
}
}
测试
@Test
public void test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Preson preson = context.getBean("preson", Preson.class);
preson.aopTest();
}
测试结果
spring整合mybatis
导入依赖
<!-- mysql数据库驱动依赖-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- mybatis依赖-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- mybatis与spring整合的依赖-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- druid数据库连接池 -->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
<!-- jdbc依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.4</version>
</dependency>
过滤资源 不然放在src/main/java和src/main/resources路径下的xml和properties文件加载不到。
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
编写mybatis配置文件:mybatis-config.xml
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 日志和缓存设置-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 指定包下的类起别名(类名首字母小写),配置后XXXMapper.xml中的类类型可以使用别名,没有配置必须使用全类名-->
<typeAliases>
<package name="com.jie.pojo"/>
</typeAliases>
<mappers>
<package name="com.jie.mapper"/>
</mappers>
</configuration>
数据源在spring-dao.xml中配置,
spring-dao.xml
<?xml version="1.0" encoding="UTF8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--Druid数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"/>
<property name="password" value="968426"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydate?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=GMT%2B8"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!-- sqlSessionFactory,用于创建sqlSession-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 绑定Druid数据库连接池-->
<property name="dataSource" ref="dataSource" />
<!-- 绑定mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!-- sqlSession-->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" >
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
spring-dao.xml中配置数据源,注册mybatis的核心bean ,sqlSessionFactory和sqlSessionTemplate
sqlSessionFactory:绑定数据源和mybatis配置文件(mybatis配置文件中的所有设置都可以在sqlSessionFactory中配置)
sqlSessionTemplate:是sqlSession的模板,可以把它当成sqlSession使用。
applicationContext.xml中导入spring-dao.xml
<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<import resource="classpath:beans.xml"/>
<import resource="classpath:spring-dao.xml"/>
<!-- 开启aop代理模式-->
<aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>
</beans>
创建数据库
CREATE TABLE `book` (
`bookID` int NOT NULL AUTO_INCREMENT,
`bookName` varchar(100) NOT NULL,
`bookCounts` int NOT NULL,
`detail` varchar(100) DEFAULT NULL,
PRIMARY KEY (`bookID`),
KEY `bookID` (`bookID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
目录结构
BookMapper接口:声明方法
public interface BookMapper {
//增
int addBook(Book book);
//删
int delBook(@Param("bookID") int id);
//改
int updateBook(Book book);
//查
List<Book> queryBook();
List<Book> queryBookByLike(String bookName);
}
BookMapper.xml:专注于编写sql
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jie.mapper.BookMapper">
<!-- 增-->
<insert id="addBook" parameterType="book">
insert into mydate.book(bookName, bookCounts, detail) VALUES(#{bookName},#{ bookCounts},#{detail});
</insert>
<!-- 删-->
<delete id="delBook" parameterType="int">
delete from mydate.book where bookID=#{bookID};
</delete>
<!-- 改-->
<update id="updateBook" parameterType="book">
update mydate.book set bookName=#{bookName},bookCounts=#{bookCounts},detail=#{detail} where bookID=#{bookID};
</update>
<!-- 查-->
<select id="queryBook" resultType="book">
select * from mydate.book;
</select>
<select id="queryBookByLike" resultType="book" parameterType="string">
select * from mydate.book where bookName like "%"#{bookName}"%" ;
</select>
</mapper>
BookMapperImpl实现类:专注业务(对应mvc的service层,注解应该使用@Service,这里就不改了。)
@Repository
public class BookMapperImpl implements BookMapper {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
public int addBook(Book book) {
return sqlSessionTemplate.getMapper(BookMapper.class).addBook(book);
}
public int delBook(int id) {
return sqlSessionTemplate.getMapper(BookMapper.class).delBook(id);
}
public int updateBook(Book book) {
return sqlSessionTemplate.getMapper(BookMapper.class).updateBook(book);
}
public List<Book> queryBook() {
return sqlSessionTemplate.getMapper(BookMapper.class).queryBook();
}
public List<Book> queryBookByLike(String bookName) {
return sqlSessionTemplate.getMapper(BookMapper.class).queryBookByLike(bookName);
}
}
测试
@Test
public void test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookMapper bookMapper = context.getBean(BookMapperImpl.class);
List<Book> books = bookMapper.queryBook();
for (Book book:books) {
System.out.println(book.toString());
}
}
测试结果
spring中事务的使用
先看一下没有事务的情况
修改BookMapperImpl的delBook,故意添加一个错误 int a=1/0;
public int delBook(int id) {
int i = sqlSessionTemplate.getMapper(BookMapper.class).delBook(id);
int a=1/0;
return i;
}
测试
@Test
public void test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookMapperImpl bookMapper = context.getBean(BookMapperImpl.class);
bookMapper.delBook(10);
}
这个方法中出现了异常,但是数据库中的数据却被删除了。
再看配置事务的情况
spring-dao.xml中添加事务配置(同样事务一般写在spring-service.xml配置文件中,偷懒所以将它们都写在了spring-dao.xml中)
<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--Druid数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"/>
<property name="password" value="968426"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydate?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=GMT%2B8"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!-- sqlSessionFactory,用于创建sqlSession-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 绑定Druid数据库连接池-->
<property name="dataSource" ref="dataSource" />
<!-- 绑定mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" >
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<!-- 下面是添加事务的配置-->
<!-- 配置声明事务 使用Druid数据源-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务通知-->
<tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
<!-- 给哪些方法配置事务-->
<tx:attributes>
<tx:method name="addBook" propagation="REQUIRED"/>
<tx:method name="delBook"/>
<tx:method name="updateBook"/>
<tx:method name="queryBook" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 配置事务切入-->
<aop:config proxy-target-class="true">
<aop:pointcut id="txPointcut" expression="execution(* com.jie.mapper.BookMapperImpl.*(..))"/>
<aop:advisor advice-ref="transactionInterceptor" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
注意点
1.tx使用的是xmlns:tx="http://www.springframework.org/schema/tx"别用错了。
2.aop要使用cglib动态代理<aop:config proxy-target-class=“true”> 不配置proxy-target-class="true"将使用InvocationHandler动态代理可能出现如下错误:
使用.class获取bean,出现以下错误。
使用bean的name获取bean,出现以下错误。
问题分析:
Spring在进行动态代理的时候,会根据得到的类,进行自动设置使用哪种动态代理的实现方法。
1.创建的类没有实现接口时:默认使用CGLIB进行动态代理的实现。
2.创建的类使用到接口时:默认使用InvocationHandler进行动态代理的实现。
使用接口时,Spring将使用InvocationHandler进行动态代理,而使用InvocationHandler的时候,代理类会实现与被代理类的相同接口,而代理对象与被代理对象属于同层不能像上/下转型,因此报错。
而通过CGLIB成功创建的动态代理,实际是被代理类的一个子类。可以上/下转型。
测试
@Test
public void test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookMapperImpl bookMapper = context.getBean(BookMapperImpl.class);
bookMapper.delBook(8);
}
可以看到数据库中的数据并没有删除。数据回滚了。