小黑子—MyBatis:第二章

MyBatis入门2.0

  • 四 小黑子诉说Mybatis核心配置文件详情
    • 4.1 多环境
    • 4.2 Mybatis的事务管理器 - transactionManager
    • 4.3 dataSource(数据源)
      • 4.3.1 不同类型下的数据源有不同的属性
      • 4.3.2 pool 和 unpooled 的区别
      • 4.3.3 配置具体的数据库连接池对象
    • 4.4 propeties标签的配置和作用
    • 4.5 mapper
  • 六 小黑子想要在WEB中应用MyBatis——银行转账小功能(使用MVC框架模式)
    • 6.1 环境搭建
    • 6.2 后端代码实现
    • 6.3 核心业务实现
    • 6.4 Mybatis的事务控制
    • 6.5 mybatis之三大对象作用域

四 小黑子诉说Mybatis核心配置文件详情

4.1 多环境

  • 一般一个数据库会对应一个SqlSessionFactory对象
  • 一个环境environment会对应一个SqlSessionFactory对象

比如:


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    settings>

    
    
    
    <environments default="mybatisDB">
        
        
        
        <environment id="powernodeDB">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            dataSource>
        environment>

        
        <environment id="mybatisDB">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            dataSource>
        environment>
    environments>



    <mappers>
        
        <mapper resource="CarMapper.xml"/>

    mappers>
configuration>

使用:

  • CarMapper.xml

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="car">
    <insert id="insertCar">
        insert into t_car values(null,'8888','法克鱿',30.0,'2000-11-66','捞车')
    insert>
mapper>

  • 测试:
package com.powernode.mybatis.test;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;

public class ConfigurationTest {
    @Test
    public void testEnviroment () throws IOException {
        //获取SqlSessionFactoryFactory对象(采用默认的方式获取)
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

        //这种方式就是获取的默认环境
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.insert("car.insertCar");
        sqlSession.commit();
        sqlSession.close();

        //这种方式就是通过环境id来使用指定的环境
        SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"),"powernodeDB");
        SqlSession sqlSession1 = sqlSessionFactory1.openSession();
        sqlSession1.insert("car.insertCar");
        sqlSession1.commit();
        sqlSession1.close();


    }
}

小黑子—MyBatis:第二章_第1张图片
小黑子—MyBatis:第二章_第2张图片

4.2 Mybatis的事务管理器 - transactionManager

  1. 作用:配置事务管理器。指定mybatis具体使用什么方式去管理事务。
  2. type属性有两个值:
    • 第一个:JDBC:使用原生JDBC代码来管理事务
      conn.setAutoCommit(false);

      conn.commit();
    • 第二个:MANAGED:mybatis不再负责事务的管理,将事务管理交给其他的JavaEE容器
      spring
  3. 不区分大小写,但是不能写其他值,只能是二选一:
    jdbc、managed
  4. 在mybatis中提供了一个事务管理器接口:Transaction
    该接口下有两个实现类:
    • jdbcTransaction
    • managedTransaction

如果type="JDBC",那么底层会实例化JdbcTransaction对象。
如果type="MANAGED",那么底层会实例化ManagedTransaction对象
当事务管理器是:JDBC

采用JDBC的原生事务机制:

开启事务:conn.setAutoCommit(false);
处理业务…
提交事务:conn.commit();

当事务管理器是:MANAGED

交给容器去管理事务,但目前使用的是本地程序,没有容器的支持,当mybatis找不到容器的支持时:没有事务。也就是说只要执行一条DML语句,则提交一次

4.3 dataSource(数据源)

dataSource配置:

  1. dataSource被称为数据源。

  2. dataSource作用是什么?

    • 为程序提供Connection对象。(但凡是给程序提供Connection对象的,都叫做数据源。)
  3. 数据源实际上是一套规范。JDK中有这套规范: javax.sql.DataSource(这个数据源的规范,这套接口实际上是JDK规定的。)

  4. 我们自己也可以编写数据源组件,只要实现javax.sql.DataSource接口就行了。实现接口当中所有的方法。这样就有了自己的数据源。
    比如可以写一个属于自己的数据库连接池(数据库连接池是提供连接对象的,所以数据库连接池就是一个数据源)。

  5. 常见的数据源组件有哪些呢【常见的数据库连接池有哪些呢】?

    • 阿里巴巴的德鲁伊连接池:
    • druid
      c3p0
      dbcp
  6. type属性 用来指定数据源的类型,就是指定具体使用什么方式来获取Connection对象:
    type属性 有三个值:必须是三选一。
    type=“[ UNPOOLED | POOLED | JNDI ]”

    • UNPOOLED:不使用数据库连接池技术。每一次请求过来之后,都是创建新的Connection对象。
    • POOLED:使用mybatis自己实现的数据库连接池。
    • JNDI:集成其它第三方的数据库连接池。

4.3.1 不同类型下的数据源有不同的属性

  • JNDI是一套规范。谁实现了这套规范呢?大部分的web容器都实现了JNDI规范:
    例如: Tomcat、Jetty、WebLogic、WebSphere,这些服务器(容器)都实现了JNDI规范。
    JNDI是: java命名目录接口。Tomcat服务器实现了这个规范。

  • 不同配置下的属性不同,通过参考官方手册进行编辑配置

  • 提醒:正常使用连接池的话,池中有很多参数是需要设置的。设置好参数,可以让连接池发挥的更好。事半功倍的效果。

  • 具体连接池当中的参数如何配置呢?需要反复的根据当前业务情况进行测试。

           <dataSource type="JNDI">
                <property name="initial_context" value="..."/>
                <property name="data_soucre" value="..."/>
            </dataSource>

这里面不同的配置下面的这个属性都不一样,具体写什么不是个人说的算,要参考官方手册

4.3.2 pool 和 unpooled 的区别

POOLED:
这种数据源的实现利用“池”的概念将JDBC连接对象组织起来,避免了创建新的连接实例所必需的初始化和认证时间。这种处理方式很流行,能使并发Web应用快速响应请求

属性 作用
poolMaximumActiveConnections 最大的活动的连接数量。默认值10
poolMaximumIdleConnections 最大的空闲连接数量。默认值5
poolMaximumCheckoutTime 强行回归池的时间。默认值20秒
poolTimeToWait 当无法获取到空闲连接时,每隔20秒打印一次日志,避免因代码配置有误,导致傻等。(时长是可以配置的)
poolPingQuery 发送到数据库的侦测查询,用来检测连接是否工作并准备结束请求。默认是“NO PING QUERY SET”,这会导致多数据库驱动出错时返回恰当的错误信息
poolPingEnabled 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的sql语句(最好是一个速度非常快的SQL语句),默认值:false
poolMaximumLocalBadConnectionTolerance 这是一个关于坏连接容忍度的底层设置,作用于每一个参数缓存池获取一个新的连接,但是这个重新尝试的次数不应该超过poolMaximumIdleConnections与poolMaximumLocalBadConnectionTolerance之和默认值:3(新增于3.4.5)
poolPingConncetionsNotUsedFor 配置poolPingQuery的频率。可以被设置为和数据库连接超过时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测——当然仅当poolPingEnabled为true时适用)

在POOLED的数据源下开启:

@Test
    public void testDataSource() throws IOException {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // sqlSessionFactory对象:一个数据一个,不要频繁地创建改对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));

        //通过sqlSessionFactory对象可以开启多个会话
        //会话1
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        sqlSession1.insert("car.insertCar");
        sqlSession1.commit();
        sqlSession1.close();//因为要测试连接池的效果,关闭是需要的。别忘了,要不然测不出来

        //会话2
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        sqlSession2.insert("car.insertCar");
        sqlSession2.commit();
        sqlSession2.close();
    }

区别:使用连接池的话就是同一个连接,sqlSession每次关闭的时候都会把连接返回到池里面,下一次就会重新调用

UNPOOLED:
这个数据源的实现会没有请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

属性 作用
driver 这是JDBC驱动的java类全限定名(并不是JDBC驱动钟可能包含的数据源类)
url 这是数据库的JDBC URL地址
name 登录数据库的用户名
password 登录数据库的密码
defaultTransactionIsolationLevel 默认的连接事务隔离级别
defaultNetworkTimeout 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看java.sqlConnection#NetworkTimeout()的API文档以获取更多信息。作为可选项,你也可以传递属性给数据库驱动。值需在属性名加上"driver."前缀即可,例如:driver.encoding=UTF8 这将通过DriverManager.getConncetion(url,driverProperties)方法传递值为UTF8的encoding属性给数据库驱动

在UNPOOLED的数据源下开启:

@Test
    public void testDataSource() throws IOException {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // sqlSessionFactory对象:一个数据一个,不要频繁地创建改对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));

        //通过sqlSessionFactory对象可以开启多个会话
        //会话1
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        sqlSession1.insert("car.insertCar");
        sqlSession1.commit();
        sqlSession1.close();//因为要测试连接池的效果,关闭是需要的。别忘了,要不然测不出来

        //会话2
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        sqlSession2.insert("car.insertCar");
        sqlSession2.commit();
        sqlSession2.close();
    }

区别:没有用连接池的话,每次就会新建一个对象,但是连接数据库是一个进程、java虚拟机又是一个进程,每一次创建那么效率就低

小黑子—MyBatis:第二章_第3张图片

4.3.3 配置具体的数据库连接池对象

正常使用连接池的话,池中有很多参数是需要设置的。设置号参数,可以让连接池发挥的更好,事半功倍的效果

具体连接池当中的参数如何配置呢?需要反复根据当前的业务情况进行测试。比如:一个网站的并发量在晚上的时候是多少多少人,但是在某个系统的时候又是不一样的,不一样的话连接池就要有不同的配置

  • poolMaximumActiveConnections:连接池当中最多的正在使用的连接对象的数量上限,最多有10个连接活动
@Test
    public void testDataSource() throws IOException {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // sqlSessionFactory对象:一个数据一个,不要频繁地创建改对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));

        //通过sqlSessionFactory对象可以开启多个会话
        //会话1
//        SqlSession sqlSession1 = sqlSessionFactory.openSession();
//        sqlSession1.insert("car.insertCar");
//        sqlSession1.commit();
//        sqlSession1.close();//因为要测试连接池的效果,关闭是需要的。别忘了,要不然测不出来
//
//        //会话2
//        SqlSession sqlSession2 = sqlSessionFactory.openSession();
//        sqlSession2.insert("car.insertCar");
//        sqlSession2.commit();
//        sqlSession2.close();
        for (int i = 0; i < 4; i++) {
            SqlSession sqlSession = sqlSessionFactory.openSession();
            sqlSession.insert("car.insertCar");
            //不要关闭
        }
    }

20秒过后就返回直至结束

时间可以通过其他属性设置
小黑子—MyBatis:第二章_第4张图片

属性 作用
poolMaximumActiveConnections:连接池当中最多的正在使用的连接对象的数量上限 最大的活动的连接数量。默认值10
poolMaximumIdleConnections 最大的空闲连接数量。默认值5
poolMaximumCheckoutTime 强行回归池的时间。默认值20秒
poolTimeToWait 当无法获取到空闲连接时,每隔20秒打印一次日志,避免因代码配置有误,导致傻等。(时长是可以配置的)

当然,还有其他属性。对于连接池来说,以上几个属性比较重要。
最大的活动的连接数量就是连接池连接数量的上限。默认值10,如果有10个请求正在使用这10个连接,第11个请求只能等待空闲连接。
最大的空闲连接数量。默认值5,如何已经有了5个空闲连接,当第6个连接要空闲下来的时候,连接池会选择关闭该连接对象。来减少数据库的开销。
需要根据系统的并发情况,来合理调整连接池最大连接数以及最多空闲数量。充分发挥数据库连接池的性能。【可以根据实际情况进行测试,然后调整一个合理的数量。】

小黑子—MyBatis:第二章_第5张图片

4.4 propeties标签的配置和作用

mybatis提供了更加灵活的配置,连接数据库的信息可以单独写到一个属性资源文件中,假设在类的根路径下创建jdbc.properties文件,配置如下:

jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/powernode
jdbc.username = root
jdbc.password = root

在mybatis核心配置文件中引入并使用:


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>









    <properties resource="jdbc.properties"/>


    
    
    
    <environments default="powernodeDB">
        
        
        
        <environment id="powernodeDB">


            <transactionManager type="JDBC"/>


            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>


                <property name="poolMaximumActiveConnections" value="10"/>



            dataSource>







        environment>

        
        <environment id="mybatisDB">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            dataSource>
        environment>
    environments>



    <mappers>
        
        <mapper resource="CarMapper.xml"/>
    mappers>
configuration>

测试成功:

properties两个属性:

  1. resource:这个属性从类的根路径下开始加载。【常用的。】
  2. url:从指定的url加载,假设文件放在d:/jdbc.properties,这个url可以写成格式:file:///d:/jdbc.properties。注意是三个斜杠。但是这种方式不建议,因为路径定死了

注意:如果不知道mybatis-config.xml文件中标签的编写顺序的话,可以有两种方式知道它的顺序:

  • 第一种方式:查看dtd约束文件。
  • 第二种方式:通过idea的报错提示信息。【一般采用这种方式】

4.5 mapper

mapper标签用来指定SQL映射文件的路径,包含多种指定方式,这里先主要看其中两种:

  • 第一种:resource,从类的根路径下开始加载【比url常用】
<mappers>
  <mapper resource="CarMapper.xml"/>
mappers>

如果是这样写的话,必须保证类的根下有CarMapper.xml文件。
如果类的根路径下有一个包叫做test,CarMapper.xml如果放在test包下的话,这个配置应该是这样写:

<mappers>
  <mapper resource="test/CarMapper.xml"/>
mappers>
  • 第二种:url,从指定的url位置加载
    假设CarMapper.xml文件放在d盘的根下,这个配置就需要这样写:
<mappers>
  <mapper url="file:///d:/CarMapper.xml"/>
mappers>

六 小黑子想要在WEB中应用MyBatis——银行转账小功能(使用MVC框架模式)

目标:

  • 掌握mybatis在web应用中怎么用
  • mybatis三大对象(sqlSession、factory、builder)的作用域和生命周期
  • ThreadLocal原理及使用
  • 巩固MVC架构模式
  • 为学习MyBatis的接口代理机制做准备

实现功能:

  • 银行账户转账

使用技术:

  • HTML + Servlet + MyBatis

WEB应用的名称:

  • bank

需求描述:
小黑子—MyBatis:第二章_第6张图片

数据库表的设计和准备数据:
小黑子—MyBatis:第二章_第7张图片
小黑子—MyBatis:第二章_第8张图片

6.1 环境搭建

web.xml

  • 对应web.xml自动配置的有点低,替换:


<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"
         metadata-complete="true">

web-app>

pop

  • 在pop里导入好依赖包,比如:
 <dependencies>

    <dependency>
      <groupId>org.mybatisgroupId>
      <artifactId>mybatisartifactId>
      <version>3.5.10version>
    dependency>

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

    <dependency>
      <groupId>ch.qos.logbackgroupId>
      <artifactId>logback-classicartifactId>
      <version>1.2.11version>
    dependency>

    <dependency>
      <groupId>javax.servletgroupId>
      <artifactId>javax.servlet-apiartifactId>
      <version>4.0.1version>
    dependency>
  dependencies>
  • 配置好tomcat

index.xml

  • 准备页面:
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>银行账号转账title>
head>
<body>
<form action="/bank/transfer" method="post">
    转出账号: <input type="text" name="fromActno"><br>
    转入账号:<input type="text" name="toActno"><br>
    转账金额:<input type="text" name="money"><br>
    <input type="submit" value="转账">
form>

body>
html>
  • 配置好源文件resources
    小黑子—MyBatis:第二章_第9张图片

  • 创建pojo包、service包、dao包、web包、utils包
    utils包

    • com.powernode.bank.utils:将之前编写的SqlSessionUtil工具类拷贝到该包下
package com.powernode.bank.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;

//mybatis工具类
public class SqlSessionUtil {
    private SqlSessionUtil(){}

    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

}

写好的工具类,方便在dao层获取sqlsession对象。

pojo包

  • com.powernode.bank.pojo
    Account类
package com.powernode.bank.pojo;

/**
 * @description: 账户类,封装账户数据
 * @author 小黑子
 * @date 2023/9/23 15:51
 * @version 1.0
*/
public class Account {
    private Long id;
    private String actno;
    private Double balance;

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }

    public Account(Long id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }

    public Account() {
    }
}

pojo是封装类,账号的类型里面有的相关数据,一方面能在java中进行数据操作,另一方面对接MySQL的账户信息,方便CRUD。

6.2 后端代码实现

service包

  • com.powernode.bank.service
    AccountServic
package com.powernode.bank.service;

/**
 * 业务类当中的业务方法的名字在起名的时候,最好见名知意,能够体系出具体的业务是做什么的
 * @description: 账户业务类
 * @author 小黑子
 * @date 2023/9/23 16:40
 * @version 1.0
*/
public interface AccountService {
    /**
     * fromActno 转出账户
     * toActno 转入账号
     * money 转账金额
    */

    void transfer(String fromActno,String toActno, double money);
}

  • com.powernode.bank.service.impl
    AccountServiceImpl
package com.powernode.bank.service.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.exceptions.TransferException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;

public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao = new AccountDaoImpl();
    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
        //1、判断转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money){
            //2、如果转出账户余额不足,提示用户
            throw new MoneyNotEnoughException("对不起,您的余额不足");
        }
        //3、如果转出账户余额充足,更新转出账户余额(update)
        //先更新内存中java对象account的余额
        Account toAct = accountDao.selectByActno(toActno);
        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);
        int count = accountDao.updateByActno(fromAct);
        //4、更新转入账户余额(update)
        count += accountDao.updateByActno(toAct);
        if (count != 2){
            throw new TransferException("转账异常,未知原因");
        }
    }

}



service层是业务逻辑层,主要是对数据进行逻辑的处理,页面的改变等。这里的逻辑主要就是对余额先进行能否转移的判断,其次是对账户余额的转移改变,最后就是对不同错误的出现返回不同的错误类型

dao包

  • com.powernode.bank.dao
    AccountDao
package com.powernode.bank.dao;

import com.powernode.bank.pojo.Account;

/**
 * @description: 账户的DAO对象.负责t_cat表中数据的CRUD
 * DAO对象中的任何一个方法和业务不挂钩.没有任何业务逻辑在里面
 * DAO中的方法就是做crud的.所以方法名大部分是:insertXXX  deleteXXX updateXXX
 * @author 小黑子
 * @date 2023/9/23 17:42
 * @version 1.0
*/
public interface AccountDao {
    /*
     * @description: 根据账号查询账户信息
     * @param actno 账号
     * @return 账户信息
    */
    Account selectByActno(String actno);

    /*
     * @description: 更新账户信息
     * @param act 被更新的账户对象
     * @return 1表示更新成功 其他值表示失败
    */
    int updateByActno(Account act);
}

  • com.powernode.bank.dao.impl
    AccountDaoImpl
package com.powernode.bank.dao.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountDaoImpl implements AccountDao {
    @Override
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
        sqlSession.close();
        return account;
    }

    @Override
    public int updateByActno(Account act) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int count = sqlSession.update("account.updateActno",act);
        sqlSession.commit();
        sqlSession.close();
        return count;
    }
}

分析dao中至少要提供几个方法,才能完成转账:

转账前需要查询余额是否充足:selectByActno
转账时要更新账户:update

dao层是三层架构的最底层,是数据访问层,主要是连接MySQL,并做数据的CRUD,不包含任何的逻辑

6.3 核心业务实现

web包

  • com.powernode.bank.web
package com.powernode.bank.web;

import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.exceptions.TransferException;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.service.impl.AccountServiceImpl;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
    //为了让这个对象在其他方法中也可以用
    private AccountService accountService = new AccountServiceImpl();
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException{
        //获取表单数据
        String fromActno = request.getParameter("fromActno");
        String toActno = request.getParameter("toActno");
        double money = Double.parseDouble(request.getParameter("money"));

        try {
            //调用servlet的转账方法完成转账。(调业务层)
            accountService.transfer(fromActno,toActno,money);
            //程序能够走到这里,表示转账一定成功了
            //调用view完成展示结果
            response.sendRedirect(request.getContextPath() + "/success.html");
        } catch (MoneyNotEnoughException e) {
            response.sendRedirect(request.getContextPath() + "/error1.html");
        } catch (TransferException e) {
            response.sendRedirect(request.getContextPath() + "/error2.html");
        }
    }
}

展示层,获取表单数据以及针对不同情况错误的出现展示不同的页面信息。

exceptions报错包

  • com.powernode.bank.exception
    MoneyNotEnoughException
package com.powernode.bank.exceptions;
/*
 * @description: 余额不足异常
*/
public class MoneyNotEnoughException extends Exception{
    public MoneyNotEnoughException(){}

    public MoneyNotEnoughException(String msg){
        super(msg);
    }
}

TransferException

package com.powernode.bank.exceptions;

public class TransferException extends Exception{
    public TransferException(){}

    public TransferException(String msg){

    }
}

提示页面

  • 报错html与成功html都类似
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>转账报告title>
head>
<body>
<h1>余额不足!!h1>
body>
html>

转账成功:
小黑子—MyBatis:第二章_第10张图片

小黑子—MyBatis:第二章_第11张图片
小黑子—MyBatis:第二章_第12张图片
超出余额:
小黑子—MyBatis:第二章_第13张图片
小黑子—MyBatis:第二章_第14张图片

6.4 Mybatis的事务控制

为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到ThreadLocal当中。

  • 修改SqlSessionUtil工具类:
package com.powernode.bank.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;

//mybatis工具类
public class SqlSessionUtil {
    private SqlSessionUtil(){}

    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //全局的,事务器级别的,一个服务器当中定义一个即可
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();


    /*
     * @description: 获取会话对象
     * @return:会话对象
     */
    public static SqlSession openSession(){

        SqlSession sqlSession = local.get();
        if(sqlSession == null){
            sqlSession  = sqlSessionFactory.openSession();
            // 奖sqlSession对象绑定到当前线程上
            local.set(sqlSession);

        }
        return sqlSession;
    }

    /*
     * @description: 关闭sqlSession对象(从当前线程中异常SqlSession对象)
     * @version 1.0
     */
    public static void close(SqlSession sqlSession){
        if (sqlSession == null) {
            sqlSession.close();
            // 注意移除SqlSession对象和当前线程的绑定关系
            // 因为Tomcat服务器支持线程池。也就是说:用过的线程对象t1,可能下一次还会使用这个t1线程
            local.remove();
        }
    }
}
  • 修改dao中的方法:AccountDaoImpl中所有方法中的提交commit和关闭close代码全部删除。
package com.powernode.bank.dao.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountDaoImpl implements AccountDao {
    @Override
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account account = (Account) sqlSession.selectOne("account.selectByActno",actno);
        return account;
    }

    @Override
    public int updateByActno(Account act) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int count = sqlSession.update("account.updateActno", act);
        return count;
    }
}
  • AccountServiceImpl进行异常模拟:
package com.powernode.bank.service.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.exceptions.TransferException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao = new AccountDaoImpl();

    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
        //添加事务控制代码
        SqlSession sqlSession = SqlSessionUtil.openSession();


        //1.判断转出账号的余额是否充足(select)
        Account fromAct = accountDao.selectByActno(fromActno);
        if(fromAct.getBalance() < money){
            //2.如果转出账户余额不足,提示用户
            throw new MoneyNotEnoughException("余额不足!");
        }

        //3. 如果转出账号余额充足,更新转出账户余额(update)
        //先更新内存中java对象account的余额
        Account toAct = accountDao.selectByActno(toActno);
        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);
        int count = accountDao.updateByActno(fromAct);

        //模拟异常
        String s = null;
        s.toString();

        //4.更新转入账户余额(update)
        count += accountDao.updateByActno(toAct);
        if(count != 2){
            throw new TransferException("转账异常!!,未知原因");
        }

        //提交事务
        sqlSession.commit();

        //关闭事务
        SqlSessionUtil.close(sqlSession);

    }
}

  • web包下AccountServlet补充异常代码:
package com.powernode.bank.web;

import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.exceptions.TransferException;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.service.impl.AccountServiceImpl;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
    //为了让这个对象在其他方法中也可以用
    private AccountService accountService = new AccountServiceImpl();
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException{
        //获取表单数据
        String fromActno = request.getParameter("fromActno");
        String toActno = request.getParameter("toActno");
        double money = Double.parseDouble(request.getParameter("money"));

        try {
            //调用servlet的转账方法完成转账。(调业务层)
            accountService.transfer(fromActno,toActno,money);
            //程序能够走到这里,表示转账一定成功了
            //调用view完成展示结果
            response.sendRedirect(request.getContextPath() + "/success.html");
        } catch (MoneyNotEnoughException e) {
            response.sendRedirect(request.getContextPath() + "/error1.html");
        } catch (TransferException e) {
            response.sendRedirect(request.getContextPath() + "/error2.html");
        }catch (Exception e){
            response.sendRedirect(request.getContextPath() + "/error2.html");
        }
    }
}


执行:
小黑子—MyBatis:第二章_第15张图片

小黑子—MyBatis:第二章_第16张图片

6.5 mybatis之三大对象作用域

  • SqlSessionFactoryBuilder

    • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
  • SqlSessionFactory

    • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
  • SqlSession

    • 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

下面的示例就是一个确保
SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

你可能感兴趣的:(mybatis,mybatis)