Spring详解

Spring详解,第1张

Spring解决企业应用开发的复杂性 histroy

创始人:Rod Johnson

spring理念使现有技术更加容易使用,像是一个粘合剂

SSM: Spring+SpringMVC+MyBatis

官网: Spring | Home 中文网: Spring 中文网 (springref.com)

官方下载地址: https://repo.spring.io/ui/native/libs-release-local/org/springframework/spring/

GitHub上的Spring项目: https://github.com/spring-projects/spring-framework

官方API:https://spring.io/projects/spring-framework#learn
官方文档阅读**:https://docs.spring.io/spring-framework/docs/current/reference/html/**

核心技术文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core

maven:spring-webmvc,spring-jdbc

<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-webmvcartifactId>
  <version>5.3.14version>
dependency>

<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-jdbcartifactId>
  <version>5.3.13version>
dependency>

优点

轻量级,非入侵式(不会改变原本代码)

控制反转(IOC),面向切面编程(AOP)

支持事务和框架整合


七大组件

现代化的java开发,就是基于spring的开发

Spring七大组件

Spring七大组件_四舍五入不如六的博客-CSDN博客(https://blog.csdn.net/xuemin__/article/details/103325402)


IOC控制权反转
  1. IOC是一种设计思想,DL(依赖注入)是实现IOC的一种方法.
  2. 是一种通过描述与第三方,生产或获取,特定对象的方式.
  3. 在Spring中由IOC容器实现,实现方法是DL
原型

原先的对象,由我们(程序员)来决定是什么样的

UserDao userDao = new UserDaoImpl();

现在的对象,传进来,不再直接由我们来决定了.控制权交给别人了

public interface UserDao {}
public class UserDaoImpl implements UserDao{}
public class UserService {
  
    //这是以前的方式,控制权在我们
    private UserDao oldUserDao = new UserDaoImpl();

    UserDao userDao;

  	//现在控制权不在我们手里了,控制层传进来什么就是什么
    public void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }

}

这几个类全部同包

public class Servlet {
  public static void main(String[] args) {
    
    UserDaoImpl userDao = new UserDaoImpl();

    UserService userService = new UserService();
    userService.setUserDao(userDao);

  }


}

Hello Spring

包结构:com.changGe.li.pojo

User实体类

import lombok.Data;

@Data
public class User{

  private String names;
  
  private Student student;
  
}

Student

public class Student { }

Spring配置文件:beans.xml

要放在resource包下

官方模板


<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="..." class="...">  
        
    bean>

    <bean id="..." class="...">
        
    bean>

    

beans>

我们自己写的


<bean id="student" class="com.changGe.li.pojo.Student"/>

<bean id="user" class="com.changGe.li.pojo.User">
  
  <property name="names" value="李长歌"/>

  
  <property name="student" ref="student"/>

bean>

测试使用

import com.changGe.li.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {

  @Test
  public void sprintTest(){
    //读取配置文件,获取Spring容器
    ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

    //从容器中,得到名叫user的对象,类型是User
    User user = application.getBean("user", User.class);

    String name = user.getNames();//李长歌
    System.out.println(name);
  }

}

ClassPathXmlApplicationContext一直向上找,可以看到途中实现了一个DefaultResourceConifg类,这个类就是用来通过流,读取配置文件的.

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

IOC创建方式 Spring的IOC:对象的一切交由Spring *** 作,如创建,管理,赋值等.

默认调用无参构造器创建

上面的配置,给User类加上全参构造器后,就报错:找不到这样的类

@Data
@AllArgsConstructor
public class User{

  private String names;

  private Student student;

}

<bean id="user" class="com.changGe.li.pojo.User">
  
  <constructor-arg index="0" value="李长歌"/>
  <constructor-arg type="java.lang.String" value="李长歌"/>
  <constructor-arg name="names" value="李长歌"/>
bean>

把spring容器理解成一个堆内存

User user = application.getBean("user",User.class);
User user1 = application.getBean("user",User.class);

System.out.println(user == user1);//true

Spring配置

别名

<alias name="user" alias="user1"/>

原先的和别名都能使用

User user = application.getBean("user",User.class);
User user1 = application.getBean("user1",User.class);

System.out.println(user == user1);//true

name可以有多个别名

<alias name="user" alias="user1"/>


<bean id="user" class="com.changGe.li.pojo.User" name="user1,user2 user3; use4">
  <constructor-arg index="0" value="李长歌"/>
bean>
User user = application.getBean("use4",User.class);
User user1 = application.getBean("user3",User.class);

System.out.println(user == user1);//true

import(导入)多人开发时,最终将所有的配置文件,导入联合在一起

beans.xml只有一个user的bean

<bean id="user" class="com.changGe.li.pojo.User" name="user1,user2 user3; use4">
  <constructor-arg index="0" value="李长歌"/>
bean>

aliasTest.xml导入beans.xml后,alias标签就不会报红了

<import resource="beans.xml"/>
<alias name="user"  alias="user1"/>

依赖注入

构造器注入,就是前面的**set(UserDao userDao)**的方式


Set注入
@Data
@AllArgsConstructor
@ToString
public class User{
  
  private Map<String,String> card;
  private Set<String> games;
  private String wife;
  private Properties info;

}
<bean id="user" class="com.changGe.li.pojo.User">
  
  <constructor-arg name="card">
    <map>
      <entry key="name" value="李长歌"/>
      <entry key="age" value="18"/>
      <entry key="sex" value=""/>
    map>
  constructor-arg>

  <constructor-arg name="games">
    <set>
      <value>LOLvalue>
      <value>CFvalue>
      <value>CSvalue>
    set>
  constructor-arg>

  <constructor-arg name="wife">
    <null/>
  constructor-arg>

  <constructor-arg name="info">
    <props>
      <prop key="driver">com.jdbc.cj.mysql.Driverprop>
      <prop key="url">jdbc:mysql:///smbmsprop>
      <prop key="username">rootprop>
      <prop key="password">rootprop>
    props>
  constructor-arg>

bean>
User user = application.getBean("user",User.class);

System.out.println(user);
User(card={name=李长歌, age=18, sex=}, games=[LOL, CF, CS], wife=null, info={password=root, url=jdbc:mysql:///smbms, driver=com.jdbc.cj.mysql.Driver, username=root})

命名空间注入

xmlns:p=“http://www.springformework.org/schema/p”

xmlns:c=“http://www.springformework.org/schema/c”

xmlns:util=“http://www.springformework.org/schema/util”

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"


xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"

xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd


http://www.springframework.org/schema/util
https://www.springframework.org/schema/util/spring-util.xsd">

先用c,p好像没有-ref这种注入模式,直接p:wife也不能用

c-命名空间带有"-ref",而p-命名空间是不带有的,以此可以区分是字面量注入还是构造器注

spring中p命名空间和c命名空间的最大区别是什么呢?-问答-阿里云开发者社区-阿里云 (aliyun.com)

<bean id="user" class="com.changGe.li.pojo.User" c:card-ref="card" c:games-ref="games"
          c:wife="李长歌" c:info-ref="info"/>

<util:map id="card" key-type="java.lang.String" value-type="java.lang.String">
  <entry key="1" value="2"/>
util:map>

<util:set id="games">
  <value>LOLvalue>
util:set>

<util:properties id="info">
  <prop key="driver">Driverprop>
util:properties>
User(card={1=2}, games=[LOL], wife=李长歌, info={driver=Driver})

Bean的作用域

<bean scope="singleton"/>


<bean scope="prototype"/>
//测试
User user = application.getBean("user",User.class);
User user1 = application.getBean("user",User.class);

System.out.println(user==user1);

其他的作用域:request,session,application和websocket


自动装配autowire
@Data
public class User{

  private Student student;

}
public class Student { }

byName:自动在容器上下文中找,和自己对象set方法后面的值对应的bean的id

如name匹配setName()

<bean id="student" class="com.changGe.li.pojo.Student"/>

<bean id="user" class="com.changGe.li.pojo.User" autowire="byName"/>

byType自动在容器上下文中找,和自己对象属性类型相同的bean

被自动装配的bean,可以不写id

<bean class="com.changGe.li.pojo.Student"/>

<bean id="user" class="com.changGe.li.pojo.User" autowire="byType"/>

测试类都是一样的

ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

User user = application.getBean("user",User.class);

System.out.println(user.getStudent());

byName需要保证bean的id唯一性

byTyep要保证类型的唯一性


注解式自动装配@Autowired(byType式)

context约束

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/>
@Data
public class User{

  //自动装配,可以不用写set方法了
  //前提是这个student已经存在ioc容器中了,且符合byName的规则
  @Autowired
  private Student student;

}
<bean id="student" class="com.changGe.li.pojo.Student"/>
<bean id="user" class="com.changGe.li.pojo.User"/>
//注解配置应用上下文:用纯注解的方式获取IOC容器
ApplicationContext application = new AnnotationConfigApplicationContext(com.changGe.li.pojo.User.class);

User user = application.getBean("user",User.class);

System.out.println(user.getStudent());
ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

User user = application.getBean("user",User.class);

System.out.println(user.getStudent());

@Nullable规定字段可以为空
@Autowired
@Nullable
private Student student;

//@Nullable同时存在,没有影响
public void setStudent(@Nullable Student student){
  this.student = student;
}
User user = application.getBean("user",User.class);

user.setStudent(null);

//正常运行
System.out.println(user.getStudent());//null

@Autowired有个required属性,默认为true,不允许为空

public @interface Autowired {
  boolean required() default true;
}

显式定义required,使字段可以为空

//三者可以同时存在
@Autowired(required = false)
@Nullable
private Student student;

public void setStudent(@Nullable Student student){
  this.student = student;
}

@Qualifier规定使用哪个bean进行自动注入

定义了相同类型的两个bean,但是只有一个有id

<bean class="com.changGe.li.pojo.Student"/>
<bean id="student1" class="com.changGe.li.pojo.Student"/>

@Qualifier指定使用哪个bean

@Autowired
@Qualifier("student1")
private Student student;

@Resource:byName式自动装配
@Data
public class Student {

  private String name;

}

两个student对象,分别给name属性赋值

<bean id="student" class="com.changGe.li.pojo.Student">
  <property name="name" value="student"/>
bean>

<bean id="student1" class="com.changGe.li.pojo.Student">
  <property name="name" value="student1"/>
bean>
@Data
public class User{

  //两者可以同时存在,没有
  @Resource(name = "student")
  private Student student;

  @Resource(name = "student1")
  public void setStudent(Student student){
    this.student = student;
  }

}

测试结果最后是student1

ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

User user = application.getBean("user",User.class);

System.out.println(user.getStudent().getName());//student1

@Autowrite要求对象必须存在

@Resource默认先通过byName来实现,找不到名字,通过byType实现.

@Resource
private Student student;

@Resource
public void setStudent(Student student){
  this.student = student;
}

byType模式匹配时,只能有一个bean,多了就报错

<bean  class="com.changGe.li.pojo.Student">
  <property name="name" value="student1"/>
bean>

注解开发

注解必须要有aop包支持,spring-webmvc默认就有了

首行需要配置包扫描


<context:component-scan base-package="com.changGe.li.pojo"/>
<context:annotation-config/>
@Data

//相当于
@Component//确定这个类是一个组件
public class User{

  //相当于
  @Value("李长歌")
  private String name;

  @Value("李世民")
  public void setName(String name){
    this.name = name;
  }

}
ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

User user = application.getBean("user",User.class);

System.out.println(user.getName());//李世民

compoent延伸注解

@Repositroy == dao层

@Data
@Repository
public class User{

  @Value("李长歌")
  private String name;

}
User user = application.getBean("user",User.class);

System.out.println(user.getName());//李长歌

service服务层

@Data
@Service
public class User{

  @Value("李长歌")
  private String name;

}

controller控制层

@Data
@Controller
public class User{

  @Value("李长歌")
  private String name;

}

注解配置单例模式

SpringBoot:@Scope注解学习 - 寒烟濡雨 - 博客园 (cnblogs.com)

@Data
@Controller
@Scope("singleton")
public class User{

  @Value("李长歌")
  private String name;

}
User user = application.getBean("user",User.class);
User user1 = application.getBean("user",User.class);

System.out.println(user == user1);//true

@Scope("prototype")//false

最理想的状态是:xml管理bean,注解赋值


JavaConfig:用注解的方式配置spring

JavaConfig是Spring的一个子项目,Spring4之后,成为核心功能

public class User{}
public class Test1 {}
@Configuration//这个注解的底层源码,也是被component注解了的
@ComponentScan("com.changGe.li")//相当于扫描包
@Import(Test1.class)//导入其他的javaConfig类
public class Test{

    @Bean//相当于语句,方法名是id,返回值是个User的对象
    public User getUser(){
        return new User();
    }

}
@Test
public void sprintTest(){

  ApplicationContext application = new AnnotationConfigApplicationContext(com.changGe.li.pojo.Test.class);

  User user = application.getBean("getUser",User.class);

  System.out.println(user);

}

代理模式

能组合,就不继承

面向对象的七大原则: https://blog.csdn.net/qq_34760445/article/details/82931002

改动别人的代码,在公司,是大忌

能加层,就不要改代码


动态代理

思想本质:以前的静态代理:房东(接口)要出租房子(接口),需要找到中介(代理类).然后中介通过传进来的对象,调用传进来的对象的方法.这个就是静态代理

//接口有一个方法,需要实现
public interface UserService {

  void add();
  void delete();
  void update();
  void query();
}

动态代理本质就是静态代理中的代理类,动态生成了.

还是房东,要出租房子.本来应该需要一个代理类的.

package com.changGe.dynamicProxy;

//实现了接口的方法
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("add执行中");
    }

    @Override
    public void delete() {
        System.out.println("delete执行中");
    }

    @Override
    public void update() {
        System.out.println("update执行中");
    }

    @Override
    public void query() {
        System.out.println("query执行中");
    }

}

但是现在有个中间的工具类,实现了Invacationhanderl接口下的invoke方法.

这个方法中有三个参数:object不用管,method(就是接口中的方法),还有args(参数列表).

动态代理对象调用方法时,就自动调用的这个方法.method.invoke(这个方法要执行)(object(bject(执行哪个对象的),args(要执行的参数));

最后method.invoke返回我们一个object对象,这个不就是方法执行完后的返回值吗?

而Proxy.newProxyIntanfice(this.getClass.getClassloader(房东),target.getintanfices(接口),this(invaicationhander)),最后 的返回值就是我们要的 动态生成的 代理类.

package com.changGe.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//这是一个代理工具类:用来动态生成代理对象
public class Dynamic implements InvocationHandler {

  //要实现的接口
  private Object target;

  public void setTarget(Object target){
    this.target = target;
  }

  //传入真实角色,要动态代理的接口,怎么代理(就是我们自己的动态代理工具类)
  //返回一个动态代理对象
  public Object getProxy(){
    return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),
    this);
  }


  //ScriptKill被动态代理后,实际是调用这个方法,来动态代理他本来要实现的方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    //对代理方法进行增强
    System.out.println(method.getName()+"方法被执行了");

    //这个方法执行后,会有一个object类型的返回值
    Object invoke = method.invoke(target, args);

    System.out.println(method.getName()+"方法执行完了");


    return invoke;

  }

}

最后动态生成的代理类调用方法,就是和静态代理一样了.

package com.changGe.dynamicProxy;

public class Test {
  public static void main(String[] args) {

    //真实角色
    UserServiceImpl user = new UserServiceImpl();

    //动态代理工具类
    Dynamic dynamic = new Dynamic();
    dynamic.setTarget(user);

    //获取动态代理对象
    UserService proxy = (UserService)dynamic.getProxy();

    //动态代理对象调用方法
    /**
     * add方法被执行了
     * add执行中
     * add方法执行完了
     */
    proxy.add();

  }

}

如果我们改变了其中的target,就是修改了接口,而这个接口一般是一类业务的方法总和.

所以我们修改其中的target,就是修改了一类业务

同时,因为最后其实调用的invoke方法,我们就可以在这个方法执行的前后,增加 *** 作了,这就是方法增强

这是Spring AOP(面向切面编程)的原理


AOP面向切面编程

实际作用就是:不改变原有代码的情况下,对原有代码进行增强

通过预编译方式,和运行期动态代理,实现程序功能的 统一维护

提供声明式事务,允许用户自定义切面

AOP织入依赖

org.aspectj.aspectjweaver1.9.4
<dependency>
  <groupId>org.aspectjgroupId>
  <artifactId>aspectjweaverartifactId>
  <version>1.9.8.RC1version>
dependency>
实现方式 Spring的原生api接口实现

UserService实现了接口User

public interface User {

  void add();
  void delete();
  void query();

}
package com.changGe.li.pojo;

import com.changGe.li.pojo.User;

public class UserImpl implements User{

  @Override
  public void add() {
    System.out.println("add实现了");
  }

  @Override
  public void delete() {
    System.out.println("delete实现了");
  }

  @Override
  public void query() {
    System.out.println("query实现了");
  }

}

自定义类实现方法增强的接口,如MethodBeforeAdvice(切入点之前),AfterRetruningActive(切入点返回值后)

package com.changGe.li.pojo;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

//建议之前的方法:切入点之前的方法
public class LogBefore implements MethodBeforeAdvice {

  @Override
  public void before(Method method, Object[] args, Object target) throws Throwable {
    System.out.println("切入点之前的方法");
  }

  public void beforeTest(){
    System.out.println("切入点之前的方法的测试方法");
  }

}
package com.changGe.li.pojo;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

//afterReturningAdvice返回值后的建议:切入点返回值之后的方法
public class LogAfter implements AfterReturningAdvice {
  @Override
  public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
    System.out.println("切入点返回值之后的方法");
  }

  public void beforeTest(){
    System.out.println("切入点返回值之后的方法的测试方法");
  }

}

beans.xml中导入aop依赖:可以直接


xmlns:aop="http://www.springframework.org/schema/aop"


http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd

配置包扫描


<context:component-scan base-package="com.changGe.li.pojo"/>
<context:annotation-config/>

配置bean和aop织入

<bean id="userService" class="com.changGe.li.pojo.UserImpl"/>

<bean id="before" class="com.changGe.li.pojo.LogBefore"/>
<bean id="after" class="com.changGe.li.pojo.LogAfter"/>

<aop:config>
  
  <aop:pointcut id="pointcut" expression="execution(* com.changGe.li.pojo.User.*(..))"/>

  
  <aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
  <aop:advisor advice-ref="after" pointcut-ref="pointcut"/>

aop:config>

测试的时候,spring动态代理的接口,所以application.getBean时,传参与返回值必须是接口类型的

ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

User user = application.getBean("userService",User.class);
user.add();

aop切面定义

自定义切面类

package com.changGe.li.pojo;

public class Diy {

  public void before(){
    System.out.println("这是before方法");
  }

  public void after(){
    System.out.println("这是after方法");
  }

}

引用切面类的增强方法

<bean id="userService" class="com.changGe.li.pojo.UserImpl"/>
<bean id="diy" class="com.changGe.li.pojo.Diy"/>

<aop:config>

  
  <aop:aspect ref="diy">
    <aop:pointcut id="pointcut" expression="execution(* com.changGe.li.pojo.User.*(..))"/>

    
    <aop:before method="before" pointcut-ref="pointcut"/>
    <aop:after method="after" pointcut-ref="pointcut"/>
  aop:aspect>

aop:config>
User user = application.getBean("userService",User.class);
user.add();
//运行结果
这是before方法
add实现了
这是after方法

注解aop
package com.changGe.li.pojo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class Diy {

  @Before("execution(* com.changGe.li.pojo.User.*(..))")
  public void before(){
    System.out.println("这是before方法");
  }

  @After("execution(* com.changGe.li.pojo.User.*(..))")
  public void after(){
    System.out.println("这是after方法");
  }

  @After("execution(* com.changGe.li.pojo.User.*(..))")
  public void after1(){
    System.out.println("这是after方法1");
  }

  //环绕增强:代理对象执行方法的时候,把所有的增强方法都执行了一遍
  @Around("execution(* com.changGe.li.pojo.User.*(..))")
  //point代表获取被切入的点
  public void around(ProceedingJoinPoint point){
    System.out.println("环绕前");

    //获取签名,也就是方法名()
    Signature signature = point.getSignature();
    System.out.println("签名是:"+signature);

    Object proceed = null;
    try {
      //执行方法
      proceed = point.proceed();
    } catch (Throwable e) {
      e.printStackTrace();
    }

    System.out.println("环绕后");
    System.out.println("返回值是:"+proceed);
  }

}

开启注解增强,就不用配置其他的aop织入了


<aop:aspectj-autoproxy/>
//运行结果:可以看出:
//就是在代理对象执行方法的时候,把所有的增强方法都执行了一遍
环绕前
签名是:void com.changGe.li.pojo.User.add()
这是before方法
add实现了
这是after方法1
这是after方法
环绕后
返回值是:null

整合MyBatis

Access denied for user ‘’@‘localhost’ (using password: YES)错误解决方法

(7条消息) MySQL Access denied for user ‘root‘@‘localhost‘ (using password: YES/NO) 的原因以及解决方案_花花花菜的博客-CSDN博客

加上mybatis-spring和spring-jdbc的依赖

基本的mybatis和mysql的依赖,可能会用到的都可以导入一遍


<dependency>
  <groupId>org.mybatisgroupId>
  <artifactId>mybatis-springartifactId>
  <version>2.0.6version>
dependency>

<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-jdbcartifactId>
  <version>5.3.13version>
dependency>


<dependency>
  <groupId>mysqlgroupId>
  <artifactId>mysql-connector-javaartifactId>
  <version>8.0.27version>
dependency>


<dependency>
  <groupId>org.mybatisgroupId>
  <artifactId>mybatisartifactId>
  <version>3.5.7version>
dependency>
package com.changGe.li.pojo;

import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class Student {
  private String id;
  private String name;
  private int tid;
}
package com.changGe.li.pojo;

import java.util.List;

public interface StudentMapper {

  List<Student> getStudentList();

}

jdbc.properties

driver=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC

mybatis-config.xml


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    
    <typeAliases>
        <package name="com.changGe.li.pojo"/>
    typeAliases>
    
    <mappers>
        <package name="com/changGe/li/pojo/StudentMapper.xml"/>
    mappers>

configuration>

StudentMapper.xml


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.changGe.li.pojo.StudentMapper">

    <select id="getStudentList" resultType="student">
        select * from student;
    select>

mapper>

beans.xml整合mybatis配置


<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:property-placeholder location="classpath:jdbc.properties"/>

    
    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${driver}"/>
        <property name="url" value="${url}"/>
        
        <property name="username" value="${password}"/>
        <property name="password" value="${password}"/>
    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/changGe/li/pojo/StudentMapper.xml"/>
    bean>

beans>

测试类

import com.changGe.li.pojo.Student;
import com.changGe.li.pojo.StudentMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class SpringTest {

  @Test
  public void sprintTest(){
    try {
      ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");
      SqlSessionFactory sqlSessionFactory = application.getBean("sqlSessionFactory", SqlSessionFactory.class);

      /*//这里跟随主配置文件而加载spring的配置文件
      InputStream resourceAsStream = Resources.getResourceAsStream("beans.xml");
      SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);*/

      SqlSession sqlSession = sqlSessionFactory.openSession();

      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
      List<Student> studentList = mapper.getStudentList();

      for (Student student : studentList) {
        System.out.println(student.toString());
      }

    } catch (Exception e) {
      e.printStackTrace();
    }

  }

}

SqlSessionTemplete

SqlSessonTemplete(SqlSession模板),可以通过SqlSessionFactroy直接构建SqlSession对象


<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/changGe/li/pojo/StudentMapper.xml"/>
bean>

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
  
  <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>
//sqlSessionTemplete就是一个qlSession
SqlSession sqlSession = application.getBean("sqlSessionTemplate", SqlSession.class);

StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudentList();

for (Student student : studentList) {
  System.out.println(student.toString());
}

SqlSessionTemplete是线程安全的,他的构造方法的参数可以用sqlsessionfactroy

//SqlSessionTemplete构造器源码
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

在这里我们可以把spring.xml再次拆分,变成一个专注配置,一个专注管理bean.

bases.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:property-placeholder location="classpath:jdbc.properties"/>


    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${password}"/>
        <property name="password" value="${password}"/>
    bean>

beans>

beans.xml专注于bean管理,通过导入bases.xml实现整合mybatis


<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="bases.xml"/>

    <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/changGe/li/pojo/StudentMapper.xml"/>
    bean>

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
    bean>

beans>

SqlSessionDaosupport:SqlSessionDao支持
package com.changGe.li.pojo;

import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper{

    //SqlSessionDaoSupport的源码是:return this.sqlSessionTemplate;
    private SqlSession sqlSession = this.getSqlSession();

    private StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

    @Override
    public List<Student> getStudentList() {

        return mapper.getStudentList();
    }

}

直接测试会报错:NullPointException

@Test
public void sprintTest(){

  StudentMapper studentMapper = new StudentMapperImpl();

  List<Student> studentList = studentMapper.getStudentList();

  for (Student student : studentList) {
    System.out.println(student.toString());
  }

}

因为SqlSessionDaoSupport,底层需要注入一个SqlSessionFactroy

getSqlSession时会调用他自己的sqlSessionTemplate,但是这个变量初始并没有赋值

public final SqlSessionFactory getSqlSessionFactory() {
  return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
}
private SqlSessionTemplate sqlSessionTemplate;

他希望子类是通过传入SqlSessionFactroy的方式,创建一个SqlSessionTemplate

protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  return new SqlSessionTemplate(sqlSessionFactory);
}

正确的用法应该是这样

package com.changGe.li.pojo;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper{


    @Override
    public List<Student> getStudentList() {

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

        //获取sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = applicationContext.getBean("sqlSessionFactory", SqlSessionFactory.class);

        SqlSessionTemplate sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);

        //获取mapper
        StudentMapper mapper = sqlSessionTemplate.getMapper(StudentMapper.class);

        return mapper.getStudentList();
    }

}

或者直接在beans.xml中配置sqlSessionFactroy对象

<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/changGe/li/pojo/StudentMapper.xml"/>
bean>

<bean id="studentMapperImpl" class="com.changGe.li.pojo.StudentMapperImpl">
  
  <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>

Spring会自动注入SqlSessionDaoSupport需要的SqlSessionFactroy

我们也可以在实现接口的时候,加入我们的代码了

public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper{

    @Override
    public List<Student> getStudentList() {
        StudentMapper mapper = this.getSqlSession().getMapper(StudentMapper.class);

        //这里可以变成其他业务代码
        System.out.println("Hello Spring");
      
        return mapper.getStudentList();
    }

}
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

StudentMapper studentMapper = applicationContext.getBean("studentMapperImpl", StudentMapperImpl.class);

List<Student> studentList = studentMapper.getStudentList();

for (Student student : studentList) {
  System.out.println(student.toString());
}
//运行结果
Hello Spring
Student(id=1, name=天才第二步, tid=1)

声明式事务

Spring配置的dataSource,默认关闭事务(MyBatis在配置环境时,必须配置transactionManager type=“jdbc”/>,默认是开启事务的)

所以下面的这些代码,即使有明显的异常,最终inster也会成功.

Student实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student {
  private String id;
  private String name;
  private int tid;
}

StudentMapper

public interface StudentMapper {

  int insertStudent(Student student);
  int deleteStudentById(@Param("id")String id);

  void query();
}

StudentMapper实现类,运行insertStudent方法时,必定产生一个异常

public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper{

    @Override
    public int insertStudent(Student student) {
        StudentMapper mapper = this.getSqlSession().getMapper(StudentMapper.class);
        int i = mapper.insertStudent(student);

      	//java.lang.ArithmeticException: / by zero
        i = 1 / 0;

        return i;
    }

    @Override
    public int deleteStudentById(String id) {
        StudentMapper mapper = this.getSqlSession().getMapper(StudentMapper.class);

        return mapper.deleteStudentById(id);
    }
  
 		@Override
    public void query() {}

}

StudentMapper.xml

<insert id="insertStudent">
  insert into student values(#{id},#{name},#{tid})
</insert>

<delete id="deleteStudentById">
  delete from student where id = #{id};
</delete>
  
<select id="query"></select>

测试

StudentMapper studentMapper = applicationContext.getBean("studentMapperImpl", StudentMapper.class);

Student student = new Student("14","路飞",1);
studentMapper.insertStudent(student);

studentMapper.deleteStudentById("1");

最后insert还是被执行了


这时我们就需要配置事务是为了:保证ACID原则


<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="datasource"/>
bean>

导入aop和tx约束

<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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans 		   http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
">

<tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
  <tx:attributes>
    
    <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.ArithmeticException"/>

    
    <tx:method name="insertStudent" propagation="REQUIRED" isolation="READ_COMMITTED"/>

    <tx:method name="deleteStudentById" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="query" read-only="true"/>
  tx:attributes>

tx:advice>



<aop:config>
  
  <aop:pointcut id="point" expression="execution(* com.changGe.li.pojo.*.*(..))"/>
  
  <aop:advisor advice-ref="transactionInterceptor" pointcut-ref="point"/>
aop:config>
spring事务传播属性

Spring事务的传播:PROPAGATION_REQUIRED - 神只吃苹果 - 博客园 (cnblogs.com)

在 spring的 TransactionDefinition接口中一共定义了六种事务传播属性:

PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED – 以非事务方式执行 *** 作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的 *** 作。

前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)


事务的隔离级别

(7条消息) spring 中 isolation 和 propagation 详解_饥饿小猪的博客-CSDN博客

  1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应
  2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
  3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
  4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/langs/720918.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存