Spring AOP增强介绍、Spring与MyBatis整合

1、增强

Sring AOP通过PointCut来指定在那些类的那些方法上织入横切逻辑,取代了传统纵向继承体系重复性代码的编写方式(例如性能监视、事务管理、安全检查、缓存、日志记录等),通过Advice来指定在切点上具体做什么事情。如方法前做什么,方法后做什么,抛出异常做什么。

异常抛出增强

异常抛出增强的特点:

  • 在目标方法抛出异常时织入增强处理;
<aop:after-throwing>元素:定义异常抛出增强

代码:

/***
 * 异常增强 目标方法的类名、方法名、参数列表
 * @param jp
 * @param e 异常信息
 */
public void afterThrowing(JoinPoint jp, RuntimeException e) {
    logger.error(jp.getSignature().getName() + "方法发生异常" + e);
}
public class UserServiceImpl implements UserService {
private UserDao userDao;

//创建set访问器,用于spring注入
public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
}

public int addUser(User user) {
    System.out.println("=========在UserServiceImpl中执行了addUser()============");

    //强制抛出异常
    if (true) {
        throw new RuntimeException("异常测试输出");
    }

    return userDao.addUser(user);
  }
}

配置文件:

<aop:config>
    <aop:pointcut id="pointcut" expression="execution(* org.westos.service.*.*(..))"/>

    <aop:aspect ref="userServiceLogger">
        <aop:before method="before" pointcut-ref="pointcut"></aop:before>
        <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"></aop:after-returning>
        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"></aop:after-throwing>
    </aop:aspect>
</aop:config>

在这里插入图片描述

可以发现,这时候有一个问题,后置增强没有实现,因为出现了异常信息,程序终止执行;

最终增强

配置文件

<aop:config>
    <aop:pointcut id="pointcut" expression="execution(* org.westos.service.*.*(..))"/>

    <aop:aspect ref="userServiceLogger">
        <aop:before method="before" pointcut-ref="pointcut"></aop:before>
        <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"></aop:after-returning>
        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"></aop:after-throwing>
        <aop:after method="afterLogger" pointcut-ref="pointcut"></aop:after>
    </aop:aspect>
</aop:config>
/***
 * 最终增强 目标方法的类名、方法名、参数列表
 * @param jp
 */
public void afterLogger(JoinPoint jp) {
    logger.error("最终增强" + jp.getSignature().getName() + "访法之行结束");
}

在这里插入图片描述

最终增强类似于finally块,即使程序遇到了异常,也会执行最终增强;

环绕增强

  • 类似于前置和后置增强的结合,功能最强大的;
/***
 * 环绕增强 目标方法的类名、方法名、参数列表
 * @param jp
 * @return
 * @throws Throwable 这个方法本身就有可能出现错误,因此他抛出了异常,谁调用谁处理
 */
public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable {
    logger.info("环绕增强调用:" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法,方法入参:" + Arrays.toString(jp.getArgs()));

    try {
        Object result = jp.proceed();
        logger.info("环绕增强调用:" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法,方法返回值:" + result);
        return result;
    } catch (Throwable e) {
        logger.error("环绕增强:" + jp.getSignature().getName() + "方法发生异常" + e);
        throw e;
    } finally {
        logger.error("环绕增强:" + jp.getSignature().getName() + "方法执行结束");
    }
}

配置文件

<aop:config>
    <aop:pointcut id="pointcut" expression="execution(* org.westos.service.*.*(..))"/>

    <aop:aspect ref="userServiceLogger">
        <aop:before method="before" pointcut-ref="pointcut"></aop:before>
        <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"></aop:after-returning>
        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"></aop:after-throwing>
        <aop:after method="afterLogger" pointcut-ref="pointcut"></aop:after>
        <aop:around method="aroundLogger" pointcut-ref="pointcut"></aop:around>
    </aop:aspect>
</aop:config>

在这里插入图片描述

2、注解

  • 之前我们使用property标签进行依赖注入的时候,Spring框架会获取property的name属性,调用set构造器为该对象注入值;

  • 一个配置文件中,需要进行管理的bean的配置有很多,为了让Spring自己去加载这些Bean,实现 "零配置",引入context命名空间:

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

【具体步骤】:

1、导入命名空间:

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

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-你的版本号.xsd

2、添加自动扫描的配置,配合注解来确定加载哪些类:

<!--需要扫描的包-->
<context:component-scan base-package="org.westos.dao"></context:component-scan>
<context:component-scan base-package="org.westos.service"></context:component-scan>

<context:component-scan base-package="org.westos.dao,org.westos.service"></context:component-scan>

3、注解了dao层、注解了service层,并且进行了命名:

-------注解dao层:
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    ……
}
--------注解service层:
@Service("userService")
public class UserServiceImpl implements UserService {
	……
}

4、service层中引用了dao层的对象:

【有以下两种方式注入】:

  • 按照类型注入

@Autowired
接口可以被多个类实现,如果此时再来一个实现了userDao接口的实现类,它这就不认识了;

  • 按名字注入

@Autowired
@Qualifier("userDao")

  • 简单的写法
@Resource(name = "userDao")

5、测试类:

@Test
public void show() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
    UserService userService = (UserService) applicationContext.getBean("userService");
    User user = new User("001", "张三", "zhangsan");
    userService.addUser(user);
}

在这里插入图片描述

  • 实体层的注解配置:

1、扫描实体类所在的包:

<context:component-scan base-package="org.westos.dao,org.westos.service,org.westos.entity"></context:component-scan>

2、给实体类添加注解:

@Component("userA")
@Scope("prototype")
public class User {
    @Value("101")
    private String userCode;
    @Value("张三")
    private String userName;
    @Value("zhangsan")
    private String userPwd;

    ……
    get和set访问器

    public User(String userCode, String userName, String userPwd) {
        this.userCode = userCode;
        this.userName = userName;
        this.userPwd = userPwd;
    }

    public User() {
    }
}

3、编写测试类:

@Test
public void show1() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
    User userA = (User) applicationContext.getBean("userA");
    System.out.println(userA.getUserName());
}

在这里插入图片描述

@Scope("prototype"):标注每次都是创建一个新的对象

Scope注解

  • spring中scope指的是对象在spring容器(IOC容器)中的生命周期,也可以理解为对象在spring容器中的创建方式。

  • 目前,scope的取值有5种取值:

在Spring 2.0之前,有singletonprototype两种;

在Spring 2.0之后,为支持web应用的ApplicationContext,增强另外三种:request,session和global session类型,它们只实用于web程序,通常是和XmlWebApplicationContext共同使用。

单个scope详解

singleton(单一实例)

此取值时表明容器中创建时只存在一个实例,所有引用此bean都是单一实例。此外,singleton类型的bean定义从容器启动到第一次被请求而实例化开始,只要容器不销毁或退出,该类型的bean的单一实例就会一直存活,典型单例模式,如同servlet在web容器中的生命周期。

prototype

spring容器在进行输出prototype的bean对象时,会每次都重新生成一个新的对象给请求方,虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不再拥有当前对象的引用,请求方需要自己负责当前对象后继生命周期的管理工作,包括该对象的销毁。也就是说,容器每次返回请求方该对象的一个新的实例之后,就由这个对象“自生自灭”,最典型的体现就是spring与struts2进行整合时,要把action的scope改为prototype。对于那些不能共享使用的对象类型,应该将其定义的scope设为prototype。

request

再次说明 request,session 和 global session类型只适用于web程序,通常是和XmlWebApplicationContext共同使用。
<bean id ="requestPrecessor" class="...RequestPrecessor"   scope="request" />
Spring容器,即XmlWebApplicationContext 会为每个HTTP请求创建一个全新的RequestPrecessor对象,当请求结束后,该对象的生命周期即告结束,如同Java web中request的生命周期。当同时有100个HTTP请求进来的时候,容器会分别针对这10个请求创建10个全新的RequestPrecessor实例,且他们相互之间互不干扰,简单来讲,request可以看做prototype的一种特例,除了场景更加具体之外,语意上差不多。

session

对于web应用来说,放到session中最普遍的就是用户的登录信息,对于这种放到session中的信息,我们可以使用如下形式的制定scope为session:
<bean id ="userPreferences" class="...UserPreferences"   scope="session" />
Spring容器会为每个独立的session创建属于自己的全新的UserPreferences实例,比request scope的bean会存活更长的时间,其他的方面没区别,如果java web中session的生命周期。

global session

<bean id ="userPreferences" class="...UserPreferences"   scope="globalsession" />
global session只有应用在基于porlet的web应用程序中才有意义,它映射到porlet的global范围的session,如果普通的servlet的web 应用中使用了这个scope,容器会把它作为普通的session的scope对待。

Scope配置:

1、XML配置,进行bean的配置时,指定scope;

在这里插入图片描述
2、注解方式,在定义类的时候进行配置,但是注意这时候要为spring配置开启注解配置;

在这里插入图片描述

3、使用注解解决切面问题

  • 之前的AOP使用的都是配置的方式,接下来使用注解的方式:

1、打开注解式的切面:

<!--把注解式的切面打开-->
<aop:aspectj-autoproxy/>

2、扫描到增强类所在的包

<context:component-scan base-package="org.westos.dao,org.westos.service,org.westos.entity,org.westos.aop"></context:component-scan>

3、@Aspect 标注增强类,这里需要注意的是:该注解只是表示这个类是一个切面,但是仍然需要把该类的对象交由spring管理,否则增强是不起作用的;

<bean id="userServiceLogger" class="org.westos.aop.UserServiceLogger" scope="prototype"/>

在这里插入图片描述
4、使用注解标注切入点以及各个时期的方法:

@Aspect
public class UserServiceLogger {
    private static Logger logger= Logger.getLogger(UserServiceLogger.class);

    @Pointcut("execution(*  org.westos.service.*.*(..))")
    public void pointcut() {}

    /***
     * JoinPoint 找一下它里面的方法简介 目标方法的类名、方法名、参数列表
     * @param jp
     */
    //描述切点
    @Before("execution(*  org.westos.service.Impl.*.*(..))")
    public void before(JoinPoint jp) {
        logger.info("前置增强调用:" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法,方法入参:" + Arrays.toString(jp.getArgs()));
    }

    /***
     * 前置增强 目标方法的类名、方法名、参数列表
     * @param jp
     * @param result 获取返回结果
     */
    @AfterReturning(value = "pointcut()", returning = "result")
    public void afterReturning(JoinPoint jp, Object result) {
        logger.info("后置增强调用:" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法,方法返回值:" + result);
    }

    /***
     * 异常增强 目标方法的类名、方法名、参数列表
     * @param jp
     * @param e 异常信息*/
    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void afterThrowing(JoinPoint jp, RuntimeException e) {
        logger.info("异常增强:" + jp.getSignature().getName() + "方法发生异常" + e);
    }

    /***
     * 最终增强 目标方法的类名、方法名、参数列表
     * @param jp*/
    @After("pointcut()")
    public void afterLogger(JoinPoint jp) {
        logger.info("最终增强" + jp.getSignature().getName() + "方法执行结束");
    }

    /***
     * 环绕增强 目标方法的类名、方法名、参数列表
     * @param jp
     * @return
     * @throws Throwable 这个方法本身就有可能出现错误,因此他抛出了异常,谁调用谁处理*/
    @Around("pointcut()")
    public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable {
        logger.info("环绕增强调用:" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法,方法入参:" + Arrays.toString(jp.getArgs()));

        try {
            Object result = jp.proceed();
            logger.info("环绕增强调用:" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法,方法返回值:" + result);
            return result;
        } catch (Throwable e) {
            logger.info("环绕增强:" + jp.getSignature().getName() + "方法发生异常" + e);
            throw e;
        } finally {
            logger.info("环绕增强:" + jp.getSignature().getName() + "方法执行结束");
        }
    }
}

5、提取出切入点

@Pointcut("execution(* org.westos.service.*.*(..))")
public void pointcut() {}

@Before("pointcut()")
@AfterReturning(value = "pointcut()", returning = "result")

6、测试类:

@Test
public void show() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
    UserService userService = (UserService) applicationContext.getBean("userService");
    User user = new User("001", "张三", "zhangsan");
    userService.addUser(user);
}

在这里插入图片描述

4、Spring与MyBatis整合

方式1

1、命名空间导入txt事务控制:applicationContext.xml核心配置文最上面导入JDBC的命名空间:

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

http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd

2、spring管理数据源:

<!--1、创建数据源,管理连接-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!--设置驱动类-->
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <!--连接字符串-->
    <property name="url"
              value="jdbc:mysql://localhost:3306/shop?useUnicode=true&amp;characterEncoding=UTF-8&amp;userSSL=false&amp;serverTimezone=UTC"/>
    <!--数据账号-->
    <property name="username" value="root"/>
    <!--数据库密码-->
    <property name="password" value="123456"/>
</bean>

也可以首先读取数据库配置文件,然后进行配置;
扫描加载classpath路径下的所有properties文件,classpath路径就是target/classes文件夹下的路径:

<context:property-placeholder location="classpath*:*.properties" />
<!--配置数据源,数据库连接池使用阿里巴巴的druid连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
      destroy-method="close">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <!--最大连接数-->
    <property name="maxActive" value="60"></property>
    <!--最小连接数-->
    <property name="minIdle" value="10"></property>
    <property name="testWhileIdle" value="true"></property>
</bean>

3、spring管理sqlSessionFactory

<!--2、spring管理sqlSessionFactory,sqlSessionFactory需要数据源信息、mybatis核心配置文件信息、mapper配置文件-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--传入数据源-->
    <property name="dataSource" ref="dataSource"/>
    <!--引入MyBatis配置文件中的配置-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <!--引入mapper配置文件-->
    <property name="mapperLocations">
        <list>
            <value>
                classpath:org/westos/dao/*.xml
            </value>
        </list>
    </property>
</bean>

4、编写完整的后台与数据库交互的层:
在这里插入图片描述
dao层:

public class UserDaoImpl implements UserDao {

	private SqlSessionTemplate sqlSessionTemplate;

	public SqlSessionTemplate getSqlSessionTemplate() {
    	return sqlSessionTemplate;
	}

	public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    	this.sqlSessionTemplate = sqlSessionTemplate;
	}

	@Override
	public List<User> selectByPrimaryKey(Integer id) {
    	return sqlSessionTemplate.selectList("org.westos.dao.UserDao.selectByPrimaryKey",id);
	}

service层:

public class UserServiceImpl implements UserService {

    private UserDaoImpl userDaoImpl;

    public UserDaoImpl getUserDaoImpl() {
        return userDaoImpl;
    }

    public void setUserDaoImpl(UserDaoImpl userDaoImpl) {
        this.userDaoImpl = userDaoImpl;
    }

    @Override
    public int deleteByPrimaryKey(Integer id) {
        return userDaoImpl.deleteByPrimaryKey(id);
    }
}

5、spring通过SqlSessionTemplate作为核心的一个对象,它需要sqlSessionFactory的值,构造注入,然后调用增删改查各个方法;

sqlSessionTemplate、userdao、userservice之间依次引用:

 <!--3、配置sqlSessionTemplate、userdao、userservice 它们三个之间层层调用-->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

<bean id="userDao" class="org.westos.dao.Impl.UserDaoImpl">
    <property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
</bean>

<bean id="userService" class="org.westos.service.Impl.UserServiceImpl">
    <property name="userDaoImpl" ref="userDao"/>
</bean>

在这里插入图片描述
6、编写测试类:

测试类:

public class UserTest {
    @Test
    public void demo1() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        List<User> list = userService.selectByPrimaryKey(1);
        System.out.println(JSON.toJSONString(list, true));
    }
}

在这里插入图片描述

SqlSessionTemplate介绍

【参考原文链接】

https://www.cnblogs.com/wt20/p/10963071.html
https://www.cnblogs.com/daxin/p/3544188.html

1、SqlSessionTemplate是mybatis-spring中最核心的一个类,MyBatis暴露出的最外层接口是SqlSession,所有的操作都是借助SqlSession接口的方法来完成的。 MyBatis本身有一个默认实现类,也是我们在单独使用MyBatis时最常见的一个实现类DefalutSqlSession。而当我们将MyBatis与Spring整合时,就不再使用这个默认实现了,取而代之的是SqlSessionTemplate。与默认实现相比主要有如下区别:

  • SqlSessionTemplate是线程安全的,可以被多个DAO共享,而DefaultSqlSession线程不安全。至于线程不安全的原因显而易见,因为一个DefaultSqlSession实际可以代表一个Connection,如果在多线程中使用时,一个线程在执行数据库操作,另一个线程执行别的操作时直接将事务提交了,岂不是就乱套了。因此MyBatis官方文档建议DefaultSqlSession的最佳作用域是方法作用域。
  • SqlSessionTemplate是不支持事务以及关闭方法的,也就是commit、rollback以及close。如果显示调用这几个方法,会抛出一个异常。事务的提交回滚以及SqlSession的关闭全都由自己自动管理,不需要外部程序参与。

2、如果是常规开发模式,我们每次在使用DefaultSqlSession的时候都从SqlSessionFactory当中获取一个就可以了。但是与Spring集成以后,Spring提供了一个全局唯一的SqlSessionTemplate示例来完成DefaultSqlSession的功能,问题就是:无论是多个dao使用一个SqlSessionTemplate,还是一个dao使用一个SqlSessionTemplate,SqlSessionTemplate都是对应一个sqlSession,当多个web线程调用同一个dao时,它们使用的是同一个SqlSessionTemplate,也就是同一个SqlSession,那么它是如何确保线程安全的呢?

  • 首先,通过如下代码创建代理类,表示创建SqlSessionFactory的代理类的实例,该代理类实现SqlSession接口,定义了方法拦截器,如果调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlSessionInterceptor的invoke方法
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
}
  • 核心代码就在 SqlSessionInterceptor的invoke方法当中。
private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //获取SqlSession(这个SqlSession才是真正使用的,它不是线程安全的)
      //这个方法可以根据Spring的事物上下文来获取事物范围内的sqlSession
      final SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //调用真实SqlSession的方法
        Object result = method.invoke(sqlSession, args);
        //然后判断一下当前的sqlSession是否被Spring托管 如果未被Spring托管则自动commit
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        //返回执行结果
        return result;
      } catch (Throwable t) {
        //如果出现异常则根据情况转换后抛出
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        //关闭sqlSession
        //它会根据当前的sqlSession是否在Spring的事物上下文当中来执行具体的关闭动作
        //如果sqlSession被Spring管理,则调用holder.released(); 使计数器-1
        //否则才真正的关闭sqlSession
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }

在上面的invoke方法当中使用了2个工具方法,分别是:

SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)

SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)
  • 那么这个2个方法又是如何与Spring的事物进行关联的呢?
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    //根据sqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder,当sqlSessionFactory创建了sqlSession,就会在事务管理器中添加一对映射:key为sqlSessionFactory,value为SqlSessionHolder,该类保存sqlSession及执行方式
    SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);
 //如果holder不为空,且和当前事务同步
    if (holder != null && holder.isSynchronizedWithTransaction()) {
      //hodler保存的执行类型和获取SqlSession的执行类型不一致,就会抛出异常,也就是说在同一个事务中,执行类型不能变化,原因就是同一个事务中同一个sqlSessionFactory创建的sqlSession会被重用
      if (holder.getExecutorType() != executorType) {
        throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
      }
      //增加该holder,也就是同一事务中同一个sqlSessionFactory创建的唯一sqlSession,其引用数增加,被使用的次数增加
      holder.requested();
   //返回sqlSession
      return holder.getSqlSession();
    }
 //如果找不到,则根据执行类型构造一个新的sqlSession
    SqlSession session = sessionFactory.openSession(executorType);
 //判断同步是否激活,只要SpringTX被激活,就是true
    if (isSynchronizationActive()) {
   //加载环境变量,判断注册的事务管理器是否是SpringManagedTransaction,也就是Spring管理事务
      Environment environment = sessionFactory.getConfiguration().getEnvironment();
      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
  //如果是,则将sqlSession加载进事务管理的本地线程缓存中
        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
  //以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中
        bindResource(sessionFactory, holder);
  //将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations
        registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        //设置当前holder和当前事务同步
  holder.setSynchronizedWithTransaction(true);
  //增加引用数
        holder.requested();
      } else {
        if (getResource(environment.getDataSource()) == null) {
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
    }
    return session;
  }
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
 //其实下面就是判断session是否被Spring事务管理,如果管理就会得到holder
    SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);
    if ((holder != null) && (holder.getSqlSession() == session)) {
   //这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用
      holder.released();
    } else {
   //如果不是被spring管理,那么就不会被Spring去关闭回收,就需要自己close
      session.close();
    }
  }

其实通过上面的代码我们可以看出Mybatis在很多地方都用到了代理模式,这个模式可以说是一种经典模式,其实不紧紧在Mybatis当中使用广泛,Spring的事物,AOP,连接池技术 等技术都使用了代理技术;

方式2

1、dao层继承SqlSessionDaoSupport 类,不需要创建SqlSessionTemplate对象了;

public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {

    @Override
    public List<User> selectByPrimaryKey(Integer id) {
        //方式 2:
        return this.getSqlSession().selectList("org.westos.dao.UserDao.selectByPrimaryKey", id);
    }
}

2、XML中修改配置:

<!--整合方式2:-->
<bean id="userDao" class="org.westos.dao.Impl.UserDaoImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

<bean id="userService" class="org.westos.service.Impl.UserServiceImpl">
    <property name="userDaoImpl" ref="userDao"/>
</bean>

3、service层与测试类不变;
在这里插入图片描述
在这里插入图片描述

  • SqlSessionDaoSupport 是 一 个抽象的支持类,用来为你提供SqlSession,调用 getSqlSession()方法你会得到一个 SqlSessionTemplate,之后可以用于执行SQL 方法;

方式3

  • 之前dao层都要有一个实现类,其实可以没有这个实现类;

1、dao层不需要有实现类,只需要提供一个和mapper映射文件对应的接口就可以了;

public interface UserDao {
    List<User> selectByPrimaryKey(Integer id);
}

2、使用mapperFactoryBean对象,该对象需要一个sqlSessionFactory对象和mapper映射文件的接口全限定类名;

<!--整合方式 3:-->
<bean id="mapperFactoryBean" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <property name="mapperInterface" value="org.westos.dao.UserDao"/>
</bean>

<bean id="userService" class="org.westos.service.Impl.UserServiceImpl">
    <property name="userDaoImpl" ref="mapperFactoryBean"/>
</bean>

3、各个类之间的依赖关系如下:

在这里插入图片描述
4、测试:

在这里插入图片描述

【MapperFactoryBean 源码解析】:https://www.cnblogs.com/Joe-Go/p/10256241.html

  • MapperFactoryBean 本身实现了 FactoryBean 和 InitializingBean 两个接口,它还继承了 SqlSessionDaoSupport;
  • 它里面有两个重要的方法:setSqlSessionFactory、setSqlSessionTemplate,也就是在 Bean 加载的过程中,Spring会自动设置 Bean 的属性值;
  • SqlSessionFactoryBean 被加载时,需要 SqlSessionFactory 或者 SqlSessionTemplate 的实现类,所以在 MapperBean 加载之前,需要需要手动生成其中的一个;
  • 如果 Spring 容器中 SqlSessionFactory 和 SqlSessionTemplate 同时存在,那么 SqlSessionDaoSupport 的这两个属性只会被设置一次。

方式4

1、导入context命名空间:

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

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

2、扫描包,注意导入context的命名空间:

<!--扫描包,也就是包里面的注解都能被识别-->
<context:component-scan base-package="org.westos"></context:component-scan>

3、MapperScan 注解扫描入口:

  • 其实我们没有必要在 Spring 的 XML 配置文件中注册所有的映射器,可以使用一个 MapperScannerConfigurer , 它 将 会 查 找 类 路 径 下 的 映 射 器 并 自 动 将 它 们 创 建 成 MapperFactoryBean
<bean id="scannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="org.westos.dao"/>
</bean>
  • basePackage 属性是映射器接口文件设置基本的包路径,可使用逗号或者分号作为分隔符设置多于一个的包路径,每个映射器将会在指定的包路径中递归地被搜索到;

  • 注 意,没有必要去指定 SqlSessionFactory 或 SqlSessionTemplate , 因为 MapperScannerConfigurer 将会创建 MapperFactoryBean之后自动装配。但是如果你使用了一个以上的DataSource,自动装配可能失效。这种情况下可以适用sqlSessionFactoryBeanNamesqlSessionTemplateBeanName 属性来设置正确的 bean 名 称来使用,如下所示:

<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />

4、添加注解:

dao层:

@MapperScan
public interface UserDao {
    List<User> selectByPrimaryKey(Integer id);
}

service层:

@Service("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDaoImpl;

    public UserDao getUserDaoImpl() {
        return userDaoImpl;
    }

    public void setUserDaoImpl(UserDao userDaoImpl) {
        this.userDaoImpl = userDaoImpl;
    }

    @Override
    public List<User> selectByPrimaryKey(Integer id) {
        return userDaoImpl.selectByPrimaryKey(id);
    }
}

5、测试:

在这里插入图片描述

©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页