小黑子—MyBatis:第二章

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

4.1 多环境

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

比如:

<?xml version="1.0" encoding="UTF-8" ?>
<!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>

    <!--default表示默认使用的环境-->
    <!--默认环境是什么意思?就是当你使用mybatis创建SqlSessionFactory对象的时候,没有指定环境的话,默认使用哪个环境-->
    <!--default的值是什么就使用那个环境-->
    <environments default="mybatisDB">
        <!--其中的一个环境。连接的数据库是powernode-->
        <!--一般一个数据库会对应一个SqlSessionFactory对象-->
        <!--一个环境environment会对应一个SqlSessionFactory对象-->
        <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>

        <!--这是mybatis的另一个环境,也就是连接的数据库是另一个数据库mybatis-->
        <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>
        <!--sql映射文件创建好之后,需要将该文件路径配置到这里-->
        <mapper resource="CarMapper.xml"/>

    </mappers>
</configuration>

使用:

  • CarMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace先随意写一个-->
<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();


    }
}

在这里插入图片描述
在这里插入图片描述

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虚拟机又是一个进程,每一次创建那么效率就低

在这里插入图片描述

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秒过后就返回直至结束
在这里插入图片描述
时间可以通过其他属性设置
在这里插入图片描述

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

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

在这里插入图片描述

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核心配置文件中引入并使用:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<!--    <properties>-->
<!--        &lt;!&ndash; 这是其中的一个属性&ndash;&gt;-->
<!--        &lt;!&ndash;<property name="属性名" value="属性值"/>&ndash;&gt;-->
<!--        <property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>-->
<!--        <property name="jdbc.url" value="jdbc:mysql://localhost:3306/powernode"/>-->
<!--        <property name="jdbc.username" value="root"/>-->
<!--        <property name="jdbc.password" value="root"/>-->
<!--    </properties>-->
    <properties resource="jdbc.properties"/>


    <!--default表示默认使用的环境-->
    <!--默认环境是什么意思?就是当你使用mybatis创建SqlSessionFactory对象的时候,没有指定环境的话,默认使用哪个环境-->
    <!--default的值是什么就使用那个环境-->
    <environments default="powernodeDB">
        <!--其中的一个环境。连接的数据库是powernode-->
        <!--一般一个数据库会对应一个SqlSessionFactory对象-->
        <!--一个环境environment会对应一个SqlSessionFactory对象-->
        <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"/>
<!--                <property name="poolTimeToWait" value="2000"/>-->
<!--                <property name="poolMaximumCheckoutTime" value="10000"/>-->
<!--                <property name="poolMaximumIdleConnections" value="5"/>-->
            </dataSource>

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


        </environment>

        <!--这是mybatis的另一个环境,也就是连接的数据库是另一个数据库mybatis-->
        <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>
        <!--sql映射文件创建好之后,需要将该文件路径配置到这里-->
        <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

需求描述:
在这里插入图片描述

数据库表的设计和准备数据:
在这里插入图片描述
在这里插入图片描述

6.1 环境搭建

web.xml

  • 对应web.xml自动配置的有点低,替换:
<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<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>
<!--    mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.10</version>
    </dependency>
<!--   mysql驱动依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.30</version>
    </dependency>
<!--    logback依赖-->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.11</version>
    </dependency>
<!--    servlet依赖-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
    </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
    在这里插入图片描述

  • 创建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>

转账成功:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
超出余额:
在这里插入图片描述
在这里插入图片描述

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");
        }
    }
}


执行:
在这里插入图片描述

在这里插入图片描述

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()) {
  // 你的应用逻辑代码
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值