Spring中文文档Spring Framework 中文文档 - Spring Framework 5.1.3.RELEASE Reference | Docs4dev
Spring官方文档Core Technologies (spring.io)
如果笔记有错误欢迎提出!!!!!!!
Spring:春天—> 给软件行业带来了春天
2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
开发者Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。。。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
名词解释:
SSH : Struct2 + Spring + Hibernate! (老式
SSM : SpringMvc + Spring + Mybatis!
三个地址:
Spring官方地址:Spring | Home
Spring下载地址(当然我们用不到)repo.spring.io
Spring的github地址Spring (github.com)
Maven的依赖:(不知道的看前面JavaWeb视频)
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.10version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.3.10version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
优点?
总结:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的框架(容器)。
讲的不清楚。建议百度。/
(7条消息) Spring模块组成_FYHannnnnn的博客-CSDN博客_spring组成
原页面已经找不到了,这个页面已经改变了)
Spring Boot
一个快速开发的脚手架
基于SpringBoot可以快速开发单个微服务
约定大于配置!
Spring Cloud
Spring CLoud是基于SpringBoot实现的
因为现在大所述公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!承上启下的作用
弊端:发展太久了之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱”!
控制反转,将程序的主动权交给用户,程序根据用户输入的值自动(被动)选择相应代码去执行。这是Spring框架的核心内容。
这里的用户指的是那些买这个项目的人吧,他们不懂代码,因此让他们直接在配置文件中修改相应的值就可以达到代码实现不同效果。
IOC的两种策略:(来自百度百科
憋了很久,终于弄懂什么是IOC(控制反转) - 戎"码"一生 - 博客园 (cnblogs.com)
IoC原理 - 廖雪峰的官方网站 (liaoxuefeng.com)
IOC是一种编程思想,由主动编程变成了被动接收。在本视频中,狂神通过set方式作为例子。而后面实际使用其实就是使用修改xml配置文件。
利用IOC,我们不用再去程序中根据用户的要求进行改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,一句话搞定:对象是由Spring创建,管理,装配!
在此过程中,对象仅通过构造函数参数,工厂方法的参数或在构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即,与它们一起使用的其他对象) 。然后,容器在创建 bean 时注入那些依赖项。此过程从根本上讲是通过使用类的直接构造或诸如服务定位器模式之类的控件来控制其依赖项的实例化或位置的 bean 本身的逆过程(因此称为 Control Inversion)。
Spring 提供了ApplicationContext
接口的几种实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。
在大多数应用场景中,不需要实例化用户代码即可实例化一个 Spring IoC 容器的一个或多个实例。
如上图所示,Spring IoC 容器使用一种形式的配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉 Spring 容器实例化,配置和组装应用程序中的对象。
传统上,配置元数据以简单直观的 XML 格式提供,这是本章大部分内容用来传达 Spring IoC 容器的关键概念和功能的内容。
编写HelloSpring:
新建一个项目Spring_study .检查一下这里的Maven是否是自己的。
然后删掉src将该工程作为父工程,在pom.xml文件中导入上面提到的依赖。
然后在项目中新建一个子工程,Spring-01-hello。
在java中创建包com.song.pojo 在里面创建hello.java类
public class Hello {
private String str;
public String getStr() {//alt+insert快速生成
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {//ctrl+o 快速生成
return getStr();
}
}
在resources文件夹中创建一个beans.xml文件
这个文件的模板建议自己在最开始提到的官方文档(中文文档也能找到
<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">
<bean id="hello" class="com.song.pojo.Hello">
<property name="str" value="This is HelloSpring.."/>
bean>
beans>
在test文件夹中的java文件创建一个测试类 test.java
public static void main(String[] args) { //psvm + 回车快速生成
//获取Spring的上下文对象,这里是已经写死了,不能够再改变了
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象已经在Spring中了,我们只需要取出来就可以了
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
输出结果:This is HelloSpring..
运行完之后发现实体类Hello的左侧边出现了绿色的叶子,当出现绿色的叶子说明已经被Spring所托管了。如果没有就点击xml文件上方的黄色提示信息,它会自动帮助改正/
接下来狂神将上个视频中讲解IOC理论时所使用的关于用户自己选择数据库使用的例子用Spring实现:
在java的com.song.pojo包里创建UserDao接口以及UserDaoMysqlImpl类和UserDaoOracleImpl类。
public interface UserDao {
void getUser();
}
public class UserDaoMysqlImpl implements UserDao{
public void getUser(){
System.out.println("通过mysql得到数据!");
}
}
public class UserDaoOracleImpl implements UserDao {
public void getUser(){
System.out.println("通过Oracle得到数据!");
}
}
在java的com.song.service包里创建UserService接口以及UserServiceImpl类。
public interface UserService {
void getUser();
}
public class UserServiceImpl implements UserService{
private UserDao userdao;
public void setUserDao(com.song.pojo.UserDao userDao) {
userdao = userDao;
}
public void getUser() {
userdao.getUser();
}
}
beans.xml文件
<bean id="mysql" class="com.song.pojo.UserDaoMysqlImpl"/>
<bean id="oracle" class="com.song.pojo.UserDaoOracleImpl"/>
<bean id="userserviceImpl" class="com.song.service.UserServiceImpl">
<property name="userDao" ref="mysql"/>
bean>
测试类test.java
public static void main(String[] args) { //psvm + 回车快速生成
//可以通过输入 CPX+回车 快速补全 注意补全后的数据类型要自己改为 ApplicationContext
//当运行这句话时,beans.xml里的 所有类 都已经被构造出来了,就存放在容器中!!!
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在Spring容器(context)中直接取出来
UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userserviceImpl");
userServiceImpl.getUser();
}
输出结果:通过mysql得到数据!
我当时有疑惑,为什么要强转为UserServiceImpl,UserService不行吗?我试了一下,也行。。。然后在网上搜到了下面的文章,先贴在这里//
(8条消息) Spring框架getBean()方法返回对象为什么只能转成接口对象,转换成接口的实例会报错?_你好,我们在哪里见过啊!-CSDN博客
这节课讲的就是关于上面的bean.xml文件中java类注册时采用何种方式。
在pojo包里创建User类:
public class User {
private String name;
public User(String name) {//有参构造函数
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
很简单的就可以理解
index指构造方法的第几个参数 初始以0开始
<bean id="user" class="com.song.pojo.User">
<constructor-arg index="0" value="song"/>
bean>
这里不能偷懒使用String哦,必须写完整,不然爆红
<bean id="user" class="com.song.pojo.User">
<constructor-arg type="java.lang.String" value="song"/>
bean>
另外试了一下两个String参数时,他会按照bean里面的顺序挨个给两个String参数赋值。
虽然可以成功赋值,但是感觉不太好用的感觉///
<bean id="user" class="com.song.pojo.User">
<constructor-arg type="java.lang.String" value="song"/>
<constructor-arg type="java.lang.String" value="zhi"/>
bean>
这里的参数名就是有参构造函数的括号里的参数
<bean id="user" class="com.song.pojo.User">
<constructor-arg name="name" value="song"/>
bean>
看到弹幕在讨论是否能通过context中一个class对象只有一个对应的实例
得出Spring默认使用的是单例模式
,然后去网上搜了一下。
单例bean与原型bean的区别:
如果一个bean被声明为单例的时候,在处理多次请求的时候在spring容器里只实例化出一个bean,后续的请求都公用这个对象,这个对象会保存在一个map里面。当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象,所以这是个单例的。
但是对于原型(prototype)bean来说当每次请求来的时候直接实例化新的bean,没有缓存以及从缓存查的过程。
Spring为什么默认是单例模式?
为了提高性能,少创建实例,垃圾回收,缓存快速获取
单例bean的优势?
单例bean的劣势?
单例的bean一个很大的劣势就是他不能做到线程安全
,由于所有请求都共享一个bean实例,所以这个bean要是有状态的一个bean的话可能在并发场景下出现问题,而原型的bean则不会有这样问题(但也有例外,比如他被单例bean依赖),因为给每个请求都新创建实例。
转自:面试题:Spring为什么默认bean为单例? - 简书 (jianshu.com)
知识有点多看不懂的一个大佬的博客:
(8条消息) Spring中Bean的单例、多例_longzhutengyue的博客-CSDN博客
在beans.xml文件中加入这句代码,然后就会在注册对象的时候可以通过xml中的别名来当作真名字使用。
<alias name="user" alias="user2"/>
id:bean的唯一标识符,也就是相当于我们学的对象名
class:bean 对象所对应的全限定名:包名+类型
name:也是别名,而且同时name可以同时取多个别名,很随意 逗号,分号,空格都可以起到分割不同别名的作用。
<bean id="userT" class="com.xsq.pojo.UserT" name="t,t1">
<property name="name" value="erha"/>
bean>
这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个(都在同一个resources文件目录下
假设,现在项目中有多个人开发,这三个人负责不同类的开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的!使用的时候直接使用总配置就可以了。
<import resource="beans_song.xml"/>
<import resource="beans_szg.xml"/>
(9条消息) Spring的五种依赖注入方式_shadow_zed的博客-CSDN博客_spring 注入
上面IOC创建对象已经讲过了
在一个新的子工程里创建实体类,记得生成对应的get和set以及tostring方法
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String, String> card;
private Set<String> games;
private Properties info;
}
public class Address {
private String address;
public String getAddress() {
return address;
}
//记得使用alt+insert来快捷生成所需要的getset和tosting
然后beans.xml 中进行student类的注册(先对name进行赋值
<bean id="student" class="com.song.pojo.Student">
<property name="name" value="song"/>
bean>
编写测试类( test/java/test.java )
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getName());
}
}
测试成功就说明本测试环境完成
beans.xml文件补全:
<bean id="address" class="com.song.pojo.Address">
<property name="address" value="青岛"/>
bean>
<bean id="student" class="com.song.pojo.Student">
<property name="name" value="song"/>
<property name="address" ref="address"/>
<property name="books">
<array>
<value>《红楼梦》value>
<value>《三国演义》value>
<value>《西游记》value>
<value>《水浒传》value>
array>
property>
<property name="hobbies">
<list>
<value>gamesvalue>
<value>codevalue>
list>
property>
<property name="card">
<map>
<entry key="身份证" value="215345615"/>
<entry key="银行卡" value="32152156132"/>
map>
property>
<property name="games">
<set>
<value>Lvalue>
<value>Cvalue>
set>
property>
<property name="info">
<props>
<prop key="name">songprop>
<prop key="sex">男prop>
<prop key="id">20190106prop>
props>
property>
bean>
然后运行测试类 调用toSting()方法,可以做到全部输出。
p命名空间注入:使用这个注入方式需要在约束条件中加入一条约束代码
然后注册时可以比较方便…代码比较简短(emmm)
xmlns:p="http://www.springframework.org/schema/p"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.song.pojo.Address" p:address="青岛"/>
c命名空间注入:使用这个注入方式也需要在约束条件中加入一条约束代码…(和p的约束代码差不多
通过构造器注入,所以我们必须在实体类中加入有参及无参构造
xmlns:c="http://www.springframework.org/schema/c"
public class Address {
private String address;
public Address() {
}
public Address(String address) {
this.address = address;
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.song.pojo.Address" c:address="青岛"/>
浅析Spring中bean的作用域|spring|websocket|session|xml_网易订阅 (163.com)
singleton(单例):在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。
prototype(多例):每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
request:每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。
session:同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。
application:限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。
websocket:Scopes a single bean definition to the lifecycle将单个bean定义作用于WebSocket的生命周期。在Web感知的SpringApplicationContext上下文中有效。
这里所说的单例,和设计模式中所提到的单例模式不同。设计模式中的单例,是强制一个类有且只有一个对象,我们如果不通过特殊的手段,将无法为这个单例类创建多个对象。而 Spring 中的单例作用域不同,这里的单例指的是在一个 Spring 容器中,只会缓存 bean 的唯一对象,所有通过容器获取这个 bean 的方式,最终拿到的都是同一个对象。但是在不同的 Spring 容器中,每一个 Spring 容器都可以拥有单例 bean 的一个实例对象,也就是说,这里的单例限定在一个 Spring 容器中,而不是整个应用程序。并且我们依然可以通过 new 的方式去自己创建 bean 。
每次从容器中get都会产生一个新对象
关于这两个模式的选择使用scope属性来确定,Spring默认为单例模式
<bean id="address" class="com.song.pojo.Address" c:address="青岛" scope="singleton"/>
<bean id="address" class="com.song.pojo.Address" c:address="青岛" scope="prototype"/>
再然后就是两种模式下都同时get一个同样的实例类,单例模式get到的是同一个对象。
当Bean的属性很少的时候,我们对它进行配置的时候就使用很少的或者元素进行装配,但是随着工程体积的增大,Bean也可能变得复杂,这时候配置文件也会变得复杂,和 就会变得很多,写起来就会很费劲,这个时候利用自动装配就方便很多
自动装备就是自动寻找容器中的类
例如人拥有猫和狗,人可以让猫叫。
首先测试咱不使用自动装配,使用手动装配的效果。
public class Dog {
public void shout(){
System.out.println("wang!");
}
}
public class Cat {
public void shout(){
System.out.println("miao!");
}
}
public class People {
private Cat cat;
private Dog dog;//省略get,set
}
测试类test:
public static void main(String[] args) {
ApplicationContext Context = new ClassPathXmlApplicationContext("beans.xml");
People people = (People) Context.getBean("people");
people.getCat().shout();
people.getDog().shout();
}
beans.xml 手动装配:
在下面的代码中我们使用property 手动装配了人所拥有的cat类和dog类
<bean id="dog" class="com.song.pojo.Dog"/>
<bean id="cat" class="com.song.pojo.Cat"/>
<bean id="people" class="com.song.pojo.People">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
bean>
beans.xml自动装配:
在本例子中就是在people类中找到setDog(Dog dog)方法然后得到括号里面的参数dog,在beans.xml中寻找到id=dog的bean,然后自动匹配。
要求找到的bean_id必须为dog,多一个字母也不行。(
<bean id="dog" class="com.song.pojo.Dog"/>
<bean id="cat" class="com.song.pojo.Cat"/>
<bean id="people" class="com.song.pojo.People" autowire="byName"/>
在本例子中,就是在beans.xml中寻找和自己对象属性类型相同的 bean
这种方法不要求bean_id 必须为dog了,但是只适用于存在一种dog的情况。你只能养一条狗了。。。
<bean id="dog" class="com.song.pojo.Dog"/>
<bean id="cat" class="com.song.pojo.Cat"/>
<bean id="people" class="com.song.pojo.People" autowire="byType"/>
继续使用上面的子工程。
使用注解需要在beans.xml中导入comtext约束,增加注解支持。(一共在原beans.xml的基础上加了四条语句。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
beans>
这个注解是属于Spring的,测试的时候配置文件:
<bean id="dog" class="com.song.pojo.Dog"/>
<bean id="cat" class="com.song.pojo.Cat"/>
<bean id="people" class="com.song.pojo.People"/>
使用Autowired我们就可以不用编写set方法了,People类中使用@Autowired注解
public class People {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
//节省篇幅,省略了get方法,测试的时候自己加上
}
运行测试test可以发现,尽管没有对bean进行装配,但是运行成功。
自己更改配置文件后,知道首先按照byType进行自动装配,如果装配失败则进行byName自动装配。
扩展:
@Autowired(required=false):表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。
在启动Spring后,注入容器的过程中,扫描到公共方法中要注入的bean,并未找到,强行注入就会注入失败。我们又不能单独的去除改方法,
所以我们有bean就注入,没有就不注入。解决办法就是@Autowired(required=false)。
Spring 注解 @Qualifier 详细解析 - 知乎 (zhihu.com)
@Autowired加上@Qualifier 直接确定了会根据byName的方式自动装配。@Qualifier不能单独使用。
测试的时候,修改beans.xml文件:
<bean id="dog1" class="com.song.pojo.Dog"/>
<bean id="dog2" class="com.song.pojo.Dog"/>
<bean id="cat1" class="com.song.pojo.Cat"/>
<bean id="cat2" class="com.song.pojo.Cat"/>
<bean id="people" class="com.song.pojo.People"/>
然后进行测试,发现报错
再修改People类 value值匹配beans.xml里的id。(如果没有匹配的值,就会直接爆红,运行都运行不了
public class People {
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
//节省篇幅,省略了get方法,测试的时候自己加上
}
然后再进行测试,成功运行。
这个注解属于j2EE,JSR-250标准注解,推荐使用它来代替Spring专有的@Autowired注解
但是!!!我的jdk11没有这个注解!!!!
@Resource装配顺序:(–来源网上
(1)如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
(2)如果指定了name,则从Spring上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
(3)如果指定了type,则从Spring上下文中找到类型匹配的唯一bean进行装配,找不到或找到多个,都抛出异常
(4)如果既没指定name,也没指定type,则自动按照byName方式进行装配。如果没有匹配,则回退为一个原始类型(UserDao)进行匹配,如果匹配则自动装配。
@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入。
在Spring4之后,要使用注解开发,必须要保证aop的包导入了。(依赖使导入Spring-webmvc就直接全部导入了
(1)< context:annotation-config />:仅能够在已经在已经注册过的bean上面起作用。对于没有在spring容器中注册的bean,它并不能执行任何操作。
(2)< context:component-scan base-package=“XX.XX”/> :除了具有上面的功能之外,还具有自动将带有@component,@service,@Repository等注解的对象注册到spring容器中的功能。
< context:annotation-config />和 < context:component-scan>同时存在的时候,前者会被忽略。如@autowire,@resource等注入注解只会被注入一次!
创建一个子工程,然后将applicationContext.xml (就是原来的beans.xml)基础的配置一下。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.song"/>
beans>
在com.song.pojo包下创建User.java类
//@Component 组件
//加上后等价于
//注意首字母小写
@Component
public class User {
//等价于
//也可以加在set方法上面,两者同时存在时,优先使用set方法上面的
@Value("szg")
public String name;
}
写一个测试方法
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");//注意这里时小写user
System.out.println(user.name);
}
运行成功。
@Component 注解的衍生
@Component
有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!
这几个衍生注解在效果都是一样的,都是代表将某个类注册到Spring中,装配Bean。
当然,需要都在配置文件的context:component-scan指定范围内
通过scope注解中value来定义作用域,详解可以看bean的作用域
将该@Scope("singleton")
加在Component下面就行。
加上注解就不用再去配置文件中配置作用域了
@Component
@Scope("singleton")
public class User {
@Value("szg")
public String name;
public void setName(String name) {
this.name = name;
}
}
xml更加万能,适用于任何场合维护简单。
注解更加方便,但是维护起来很麻烦
两者比较合理的分配:
JavaConfig是Spring的一个子项目。它基于Java代码和Annotation注解来描述Bean之间的依赖绑定关系。
使用JavaConfig就可以实现不需要beans配置文件,就可以将对象注册到容器中。
@Component
与后面的@ComponentScan
相配套使用、两者一起出现
这个注解也会注册一个Bean,id属性为实体类的小写
@Component
public class User {
public String name;
@Value("szg")
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
@Configuration
将该文件标记为配置类(代替xml配置文件
@Bean
作为Spring的XML配置文件中的
这个方法的名字就相当于bean中的id属性
这个方法的返回值,就相当于bean标签中的class属性
(ps:可以使用@Bean("BeanName")
来手动指定Bean的名字)
@ComponentScan("com.song.pojo")
相当于之前配置文件的
@Import
相当于之前的 xml配置文件里的 标签
@Configuration
@ComponentScan("com.song.pojo")
@Import("Song_Config2.class")
public class Song_Config {
@Bean
public User getUser(){
return new User();
}
}
@Bean
注解默认作用域为单例singleton 作用域,可通过@Scope(”prototype“)
设置为原型作用域@Bean
的作用是注册bean对象,那么完全可以使用@Component
、@Controller
、@Service
、@Ripository
等注解注册bean,当然需要配置@ComponentScan
注解进行自动扫描。这里因为我们用的是java配置类去做,所以通过AnnotationConfigApplicationContext
类进行解析并注册到Bean的注册表,通过java配置类的class对象加载。
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Song_Config.class);
User user1 = (User) context.getBean("getUser");
User user2 = (User) context.getBean("user");
System.out.println(user1);
System.out.println(user2);
System.out.println(user1==user2);//两个不一样的user对象
System.out.println(user1.hashCode());
System.out.println(user2.hashCode());
}
要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。
例子:房东想要出租房子,但是没时间,因此扔给了中介,中介需要帮助房东实现出租房子的任务。
在这里中介就是静态代理了房东来做出租房子这件事情。
租房子的接口:
public interface Rent {
public void rent();
}
房东:
public class Host implements Rent{
public void rent() {
System.out.println("房东出租房子!");
}
}
中介:
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void rent() {//代理房东实现出租
System.out.println("中介发放出租房源");
host.rent();
//可以在函数里面加一些附加的内容
System.out.println("房东记得给中介费!");
}
}
房子被出租:
public class Test {
public static void main(String[] args) {
Host host = new Host();
//代理,中介帮房东出租房子,并且代理角色也可以加上一些附有的操作(比如签合同、收费等)!
Proxy proxy = new Proxy(host);
//不用面对房东,直接找中介租房即可!
proxy.rent();
}
}
再例如可以在上面的代理类中对于每种操作都进行输出(printf)来达到了不通过修改房东的源代码而实现了输出关于房子出租过程的作用。
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:
建议先看前面的注解和反射的视频再继续向下看。
动态代理和静态代理角色一样
动态代理的代理类是动态生成的,不是我们手动写好的
动态代理分为俩大类:基于接口的动态代理,基于类的动态代理
首先我们需要了解俩个类:Proxy:代理,InvocationHandler:调用处理程序
(13条消息) java中getClass()方法简介_expect521的博客-CSDN博客_getclass
(13条消息) 动态代理模式newProxyInstance及invoke方法参数详解_mRambo的博客-CSDN博客
主要是理解 InvocationHandler newProxyInstance .
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
关于代码:
租房子的接口 和 房东对象 都是和前面一样
动态代理类:
public class AutoProxy implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent){
this.rent=rent;
}
//生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
//格式几乎是死的 注意狂神写的时候第一个参数写的this.getClass().getClassLoader()
public Object getProxy(){
//三个参数 :代理类的类加载器 代理类的接口列表 调度方法调用的调用处理程序(就是自己
return Proxy.newProxyInstance(rent.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
// 处理代理实例上的方法调用并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:本质利用反射实现!
Object result = method.invoke(rent, args);
fare();
return result;
}
public void seeHouse(){
System.out.println("带房客看房");
}
public void fare(){
System.out.println("记得给中介费!");
}
}
public class Client {
//一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
//由代码动态的生成 接口相应的 代理类
public static void main(String[] args) {
//真实角色 对应接口的实现类
Host host = new Host();
//动态代理 :代理实例的调用处理程序
AutoProxy ap = new AutoProxy();
//多态 因为host实现了rent的接口
ap.setRent(host); //将真实角色放置进去
// 动态生成对应的代理类! 记得强转一下
Rent proxy = (Rent)ap.getProxy();
proxy.rent();
}
}
我们来使用动态代理实现代理我们后面写的UserService!
我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
//真实对象
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
//模板 直接可以拿过来用!
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){ //JDK 动态代理的局限性,只支持接口
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// proxy : 生成的代理类
// method : 代理类的调用处理程序的方法对象.
// 被代理对象 target 调用其自身方法时都会执行 invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());//返回调用的函数方法的名称
Object result = method.invoke(target, args);
return result;
}
public void log(String methodName){
System.out.println("执行了"+methodName+"方法");
}
}
public class Test {
public static void main(String[] args) {
//每次只需要更改真实对象 而不用每次都要写一个静态代理的代理类
UserServiceImpl userService = new UserServiceImpl();
//代理对象的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
proxy.delete();
proxy.query();
}
}
动态代理的好处:
代理模式核心是AOP思想:(改人源代码相当于刨祖坟(–来自弹幕
因此当需要对源代码的业务实现进行修改时,需要面向切片编程
给源代码套个娃,即再写一个套娃的代理类,从而对源代码的业务进行修改.
AOP 实现机制 - 简书 (jianshu.com)
AOP(Aspect Oriented Programming):可以通过预编译方式和运行其动态代理实现在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术。
AOP是c(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
提供声明式事务;允许用户自定义切面
以下名词需要了解下: (狂神的理解-)
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):切面通知 执行的 “地点”的定义。
连接点(JointPoint):与切入点匹配的执行点。
理解几种增强方式!
Spring中的5种Aop常见应用方式 - 知乎 (zhihu.com)
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.7version>
dependency>
public interface UserService {
public void add();
public void delete();
public void update();
}
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
}
public class BeforeLog implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//args : 被调用的方法的参数
//target : 目标对象
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println( target.getClass().getName() + "的" + method.getName() + "方法被执行");
}
}
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法,"+"返回值为:"+returnValue);
}
}
创建Spring基本配置文件然后编辑
<bean id="userservice" class="com.song.service.UserServiceImpl"/>
<bean id="beforelog" class="com.song.log.BeforeLog"/>
<bean id="afterlog" class="com.song.log.AfterLog"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.song.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="beforelog" pointcut-ref="pointcut"/>
aop:config>
创建Test测试文件
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
// 写接口
UserService userservice = (UserService) context.getBean("userservice");
userservice.add();
}
}
结果:
com.song.service.UserServiceImpl的add方法被执行
增加用户
执行了com.song.service.UserServiceImpl的add方法,返回值为:null
因此在没有改变UserService的代码的基础上,增加了日志!
public class DiyPointcut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
<bean id="diy" class="com.song.config.DiyPointcut"/>
<aop:config>
<aop:aspect ref="diy">
<aop:pointcut id="diyponitcut" expression="execution(* com.song.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyponitcut" method="before"/>
<aop:after pointcut-ref="diyponitcut" method="after"/>
aop:aspect>
aop:config>
然后测试即可
//如果没有Aspect的就是导入依赖生效的范围不对,去掉pom.xml文件里的scope标签然后刷新Maven就行了。
//定义切面类
@Aspect
public class AnnotationPointCut {
//如果导入了junit依赖会有junit包下的Before注解 要看清楚 不要导错了
@Before("execution (* com.song.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("====方法执行前====");
}
@After("execution(* com.song.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("----方法执行后------");
}
@Around("execution(* com.song.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable{
System.out.println("环绕前");
//签名:返回参数的类型 和 类的执行了的方法名称
System.out.println("获得签名:"+jp.getSignature());
System.out.println("环绕后");
}
}
<bean id="annotationPointcut" class="com.song.config.AnnotationPointCut"/>
<aop:aspectj-autoproxy/>
然后测试即可
结果:
环绕前
获得签名:void com.song.service.UserService.add()
====方法执行前====
增加用户
----方法执行后------
环绕后
null
在环绕增强中,可以执行业务方法,而在前置增强和后置增强中则不可以;这里可以通过环绕增强实现数据库事务的实现,也可以通过环绕增强实现程序运行时间的记录;****
通过aop命名空间的
当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被
Spring-Mybatis中文文档:mybatis-spring –
创建一个新的子工程在子工程的pom.xml导入依赖:(父工程的依赖有spring-webmvc spring-jdbc 和junit 三种依赖
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.7version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.26version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.7version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.6version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.3.12version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.22version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-apiartifactId>
<version>5.8.1version>
<scope>compilescope>
dependency>
dependencies>
然后紧接着在配置文件后面加上:
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>truefiltering>
resource>
resources>
build>
然后刷新Maven!!!!
然后连接数据库,(如果找不到database的话)点击idea右上角的搜索然后搜索出来。。。。。。(先去看Mybatis课程
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper class="com.song.mapper.UserMapper"/>
mappers>
configuration>
public interface UserMapper {
List<User> getUserList();
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.song.mapper.UserMapper">
<select id="getUserList" resultType="com.song.pojo.User">
select * from mybatis.user
select>
mapper>
public class MybatisUtils {
// 提升作用域 提升到全局变量 static只执行一次
private static SqlSessionFactory sqlSessionFactory ;
static {
try {String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
//SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}public class MybatisUtils {
// 提升作用域 提升到全局变量 static只执行一次
private static SqlSessionFactory sqlSessionFactory ;
static {
try {String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
//SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
一个快捷创建测试类的方法。。。。mapper接口里面右键。选择go to,再点test,然后create new test
public void testGetUserList() throws IOException {
//下面这句代码是直接调用了mybatis-utils文件
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user: userList){
System.out.println(user);
}
sqlSession.close();
}
测试成功。(没内容的看一下是不是自己的数据库就是空的
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。
在上面基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。
SqlSessionFactory需要一个 必要的 DataSource(数据源)属性。
一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。
这时的MyBatis 配置文件并不需要是一个完整的 ,因为后面Spring帮助它实现了很多,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession,它内置了常用的增删改出以及事务,并且更加安全。
可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath*:com/song/mapper/*.xml"/>
bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
bean>
beans>
然后将Mybatis的配置文件mybatis-config.xml里的代码全部删掉了(狂神建议保留别名(typeAliases)、设置(settings))
在com.song.mapper中创建一个实现类UserMapperImpl.java 私有化sqlSessionTemplate
public class UserMapperImpl implements UserMapper{
//整合到类里面,到时候直接调用类的方法即可。
//我们所有的操作在原来都是用sqlSession 现在采用SQL SessionTemplate
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession){
this.sqlSession=sqlSession;
}
@Override
public List<User> getUserList() {
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
return mapper.getUserList();
}
}
<bean id="userMapper" class="com.song.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
bean>
public static void main(String[] args) {
//其实就是将SqlSession工厂和SqlSession交给了Spring托管
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.getUserList()) {
System.out.println(user);
}
}
测试成功。
mybatis-spring1.2.3版以上的才有这个方法 .
官方文档截图 :
dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
测试:
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.selectUser();
}
}
<bean id="userDao" class="com.song.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
bean>
事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。
事务四个属性ACID
事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
两种事务管理:
一般情况下比编程式事务好用。
将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理
将事务管理代码嵌到业务方法中来控制事务的提交和回滚
缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
搭建测试环境:
public interface UserMapper {
List<User> getUserList();
int addUser(User user);
int deleteUser(int id);
}
<insert id="addUser" parameterType="com.song.pojo.User" >
insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd})
insert>
<delete id="deleteUser" parameterType="int" >
deletes from mybatis.user where id=#{id}
delete>
@Override
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
@Override
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userDao", UserMapper.class);
User user= new User(7,"小王","1234562");
userMapper.addUser(user);
userMapper.deleteUser(7);//这条语句一定不会成功
}
在没有开始事务之前,当代码中有错误,尽管报错但是报错之前的数据修改仍然正常进行了,
因此我们要求开启事务,若后面报错,则之前的数据要进行回溯。
<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.song.mapper.*.*(..))"/>
aop:config>
如果报错之后仍然插入成功就检查一下mysql的控制台下的表的引擎,不是innoDB的话可能不支持事务回滚。