小黑子—MyBatis:第二章

news2025/1/11 21:59:16

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对象

比如:

<?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()) {
  // 你的应用逻辑代码
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1037382.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【操作系统笔记三】内存寻址

物理寻址 主存&#xff08;内存&#xff09; 计算机主存也可以称为物理内存&#xff0c;内存可以看成由若干个连续字节大小的单元组成的数组每个字节都有一个唯一的物理地址&#xff08;Physical Address&#xff09;CPU访问内存前&#xff0c;先拿到内存地址&#xff0c;然后…

Django — 会话

目录 一、Cookie1、介绍2、作用3、工作原理4、结构5、用途6、设置7、获取 二、Session1、介绍2、作用3、工作原理3、类型4、用途5、设置6、获取7、清空信息 三、Cookie 和 Session 的区别1、存储位置2、安全性3、数据大小4、跨页面共享5、生命周期6、实现机制7、适用场景 四、P…

掌动智能浅析故障注入测试的好处与实践方法

在现代技术环境中&#xff0c;系统面临各种潜在的威胁和故障&#xff0c;如硬件故障、网络问题、软件错误等。为了应对这些挑战&#xff0c;开发团队需要确保系统在逆境中依然能够提供可靠的服务。故障注入测试是一种模拟现实故障和异常情况的方法&#xff0c;旨在提高系统的鲁…

vue3+ts+java使用WebSocket传输数据

一、环境 系统&#xff1a;win11 IDE&#xff1a;vscode 框架&#xff1a;electron22.0.0vite2vue3typescript4.8.4springboot2.2.5jdk1.8 二、websocket介绍 2.1 由来 WebSocket未出现之前&#xff0c;浏览器和服务器之间的通信是通过Web的poll技术进行通信&#xff0c;就…

牛客java训练题 day1

9.24 day1 Q 1. this 指针是用来干什么的&#xff1f; 2.基类和派生类分别是指什么&#xff1f; 3.为什么方法中不能写静态变量 4. 解释一下ASCII码和ANSI码和两者的区别 5.简述j ava.io java.sql java.awt java.rmi 分别是什么类型的包 6. 看下面一段代码&#xff1a;…

分类预测 | Matlab实现NGO-CNN-SVM北方苍鹰算法优化卷积支持向量机分类预测

分类预测 | Matlab实现NGO-CNN-SVM北方苍鹰算法优化卷积支持向量机分类预测 目录 分类预测 | Matlab实现NGO-CNN-SVM北方苍鹰算法优化卷积支持向量机分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现NGO-CNN-SVM北方苍鹰算法优化卷积支持向量机分类预…

安全防御第二次作业

1. 防火墙支持那些NAT技术&#xff0c;主要应用场景是什么&#xff1f; 防火墙支持几乎所有的NAT技术&#xff0c;包括源NAT、目标NAT、双向NAT等&#xff0c;主要应用场景是保护内部网络免受外部网络的攻击 NAT技术可以将IP数据报文头中的IP地址转换为另一个IP地址&#xff…

暴力递归转动态规划(七)

题目 LeetCode原题-最长回文子序列 给你一个字符串 s &#xff0c;找出其中最长的回文子序列&#xff0c;并返回该序列的长度。 子序列定义为&#xff1a;不改变剩余字符顺序的情况下&#xff0c;删除某些字符或者不删除任何字符形成的一个序列。 示例 1&#xff1a; 输入&a…

【【萌新的FPGA学习之按键控制LED实验】】

按键控制LED实验 在写这篇文章之前我必须对我的错误表示深刻的道歉 因为我之前的文章自己也是边看边学给大家带来了大的困扰 抱歉抱歉 我们这里讲述一下综合和仿真的关系 其实我们更多的是应该关注仿真下得到的波形情况 然后分析 对于综合&#xff0c;综合的最大的目的还是看功…

计算机等级考试—信息安全三级真题二

目录 一、单选题 二、填空题 三、综合题 一、单选题

数据结构的奇妙世界:实用算法与实际应用

文章目录 数据结构和算法的基本概念数据结构数组链表栈队列树图 算法 常见的数据结构和算法排序算法快速排序示例 数据结构的应用数据库管理系统图像处理网络路由 数据结构和算法的性能分析时间复杂度空间复杂度 如何更好地编写代码避免常见错误结论 &#x1f389;欢迎来到数据…

Qt地铁智慧换乘系统浅学( 三 )最少路径和最少换乘实现

本算法全都基于广度优先 概念最短路径实现所用容器算法思路 最少换乘实现所需容器算法思路 成果展示代码实现判断是最短路径还是最少换乘最短路径代码实现最少换乘代码实现根据所得List画出线路 ui界面的维护&#xff08;前提条件&#xff09;界面初始化combox控件建立槽函数 概…

把Eclipse整个文件夹添加到Microsoft Defender的排除项中

一.原因&#xff1a; Windows 10卫士显著降低了Eclipse的速度&#xff0c;原因是Windows 10卫士扫描JAR文件。这个问题已经报告给微软了。在此之前&#xff0c;解决此问题的一个方法是将Eclipse根目录添加到Windows 10 Defender的排除列表中&#xff0c;详细步骤在这里共享。 …

前端JavaScript入门到精通,javascript核心进阶ES6语法、API、js高级等基础知识和实战 —— JS基础(五)

接受自己原本的样子&#xff0c; 比努力扮演另一个轻松多了。 思维导图 对象 什么是对象 对象使用 遍历对象 索引号是字符串型&#xff0c;不推荐遍历数组。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><me…

docker实现mysql主从复制(巨详细!!!)

docker实现mysql主从复制&#xff08;巨详细&#xff01;&#xff01;&#xff01;&#xff09; 新建主机服务容器实例3307进入/mydata/mysql-master/conf目录下新建my.cnf修改完配置后重启master实例进入mysql-master容器master容器实例内创建数据同步用户新建 服务器容器实例…

【论文阅读】内存数据库并发控制算法的实验研究

内存数据库并发控制算法的实验研究 原文链接jos.org.cn/jos/article/pdf/6454 摘要 并发控制算法的基本思想归纳为"先定序后检验”&#xff0c;基于该思想对现有各类并发控制算法进行 了重新描述和分类总结&#xff0c;于在开源内存型分布式事务测试床 3TS 上的实际对比实…

Tune-A-Video论文阅读

论文链接&#xff1a;Tune-A-Video: One-Shot Tuning of Image Diffusion Models for Text-to-Video Generation 文章目录 摘要引言相关工作文生图扩散模型文本到视频生成模型文本驱动的视频编辑从单个视频生成 方法前提DDPMsLDMs 网络膨胀微调和推理模型微调基于DDIM inversio…

动手学深度学习(pytorch版)第二章-2.3线性代数Note-linear-algebra

类型 标量&#xff1a;仅包含一个数值被称为标量 向量&#xff1a;向量可以被视为标量值组成的列表 矩阵&#xff1a;正如向量将标量从零阶推广到一阶&#xff0c;矩阵将向量从一阶推广到二阶。 A torch.arange(20).reshape(5, 4) A.T //转置 张量&#xff1a;是描述具有…

[36c3 2019]includer

[36c3 2019]includer 题目描述&#xff1a;Just sitting here and waiting for PHP 8.0 (lolphp). 首先来了解一下临时文件包含之PHP - compress.zlib:// 在 php-src 里可以找到和 compress.zlib:// 有关的代码 | code 注意到 STREAM_WILL_CAST&#xff0c;涉及到 cast 经常…