Mybatis2

1.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>  
  
<!--    <settings>-->  
<!--        <setting name="logImpl" value="STDOUT_LOGGING" />-->  
<!--    </settings>-->  
  
    <environments default="development">  
        <environment id="development">  
            <transactionManager type="JDBC"/>  
            <dataSource type="POOLED">  
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>  
                <property name="url" value="jdbc:mysql://localhost:3306/rainsoul"/>  
                <property name="username" value="root"/>  
                <property name="password" value="root"/>  
            </dataSource>  
        </environment>  
    </environments>  
  
    <mappers>  
        <mapper resource="CarMapper.xml"/>  
        <mapper resource="CarMapper2.xml"/>  
    </mappers>  
</configuration>

MyBatis 核心配置文件说明

1.1<configuration> 根标签

表示配置信息。

1.2<environments> 环境配置

  • 可配置多个环境(以复数形式存在)。
  • default 属性:指定默认使用的环境,与 <environment>id 值对应。

1.3<environment> 具体的环境配置

  • 包括事务管理器和数据源的配置。
  • id 属性:给当前环境一个唯一标识,用于与 default 属性对应。

1.4<transactionManager> 配置事务管理器

  • type 属性:指定事务管理器的类型,可选值包括 JDBCMANAGED

1.5<dataSource> 指定数据源

  • type 属性:指定使用的数据库连接池的策略,可选值包括 UNPOOLEDPOOLEDJNDI
  • property 属性列表:
    • driver
    • url
    • username
    • password
    • 其他可选属性如 defaultTransactionIsolationLeveldefaultNetworkTimeout 等。

1.5.1POOLED 数据源

  • property 属性列表:
    • poolMaximumActiveConnections:在任意时间可存在的活动连接数量,默认值:10。
    • poolMaximumIdleConnections:任意时间可能存在的空闲连接数。

1.5.2.JNDI 数据源

  • property 属性列表(最多只包含以下两个属性):
    • initial_context:在 InitialContext 中寻找上下文。
    • data_source:引用数据源实例位置的上下文路径。

1.6<mappers> 映射器配置

  • 可配置多个 SQL 映射文件的路径。

1.7<mapper> 配置某个 SQL 映射文件的路径

  • 使用 resource 属性指定相对于类路径的资源引用方式。
  • 使用 url 属性指定完全限定的 URL 方式。

2.environment

<?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>  
    <!--默认使用开发环境-->  
    <!--<environments default="dev">-->    <!--默认使用生产环境-->  
    <environments default="production">  
        <!--开发环境-->  
        <environment id="dev">  
            <transactionManager type="JDBC"/>  
            <dataSource type="POOLED">  
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>  
                <property name="url" value="jdbc:mysql://localhost:3306/rainsoul"/>  
                <property name="username" value="root"/>  
                <property name="password" value="root"/>  
            </dataSource>  
        </environment>  
        <!--生产环境-->  
        <environment id="production">  
            <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>
@Test  
public void testEnvironment() throws Exception {  
    // 准备数据  
    Car car = new Car();  
    car.setCarNum("133");  
    car.setBrand("丰田霸道");  
    car.setGuidePrice(50.3);  
    car.setProduceTime("2020-01-10");  
    car.setCarType("燃油车");  
  
    // 一个数据库对应一个SqlSessionFactory对象  
    // 两个数据库对应两个SqlSessionFactory对象,以此类推  
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  
  
    // 使用默认数据库  
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder  
            .build(Resources.getResourceAsStream("mybatis-config.xml"));  
    SqlSession sqlSession = sqlSessionFactory.openSession(true);  
    int count = sqlSession.insert("insertCar", car);  
    System.out.println("插入了几条记录:" + count);  
  
    // 使用指定数据库  
    SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder  
            .build(Resources.getResourceAsStream("mybatis-config.xml"), "dev");  
    SqlSession sqlSession1 = sqlSessionFactory1.openSession(true);  
    int count1 = sqlSession1.insert("insertCar", car);  
    System.out.println("插入了几条记录:" + count1);  
}

执行结果:

image.png

3.transactionManager

<?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>  
    <environments default="dev">  
        <environment id="dev">  
            <transactionManager type="MANAGED"/>  
            <dataSource type="POOLED">  
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>  
                <property name="url" value="jdbc:mysql://localhost:3306/rainsoul"/>  
                <property name="username" value="root"/>  
                <property name="password" value="root"/>  
            </dataSource>  
        </environment>  
    </environments>  
    <mappers>  
        <mapper resource="CarMapper.xml"/>  
    </mappers>  
</configuration>
@Test  
public void testTransactionManager() throws Exception{  
    // 准备数据  
    Car car = new Car();  
    car.setCarNum("133");  
    car.setBrand("丰田霸道");  
    car.setGuidePrice(50.3);  
    car.setProduceTime("2020-01-10");  
    car.setCarType("燃油车");  
    // 获取SqlSessionFactory对象  
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder  
            .build(Resources.getResourceAsStream("mybatis-config2.xml"));  
    // 获取SqlSession对象  
    SqlSession sqlSession = sqlSessionFactory.openSession();  
    // 执行SQL  
    int count = sqlSession.insert("insertCar", car);  
    System.out.println("插入了几条记录:" + count);  
}

当事务管理器是:JDBC
采用JDBC的原生事务机制:

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

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

4.dataSource

<?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>  
    <environments default="dev">  
        <environment id="dev">  
            <transactionManager type="JDBC"/>  
            <dataSource type="UNPOOLED">  
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>  
                <property name="url" value="jdbc:mysql://localhost:3306/rainsoul"/>  
                <property name="username" value="root"/>  
                <property name="password" value="root"/>  
            </dataSource>  
        </environment>  
    </environments>  
    <mappers>  
        <mapper resource="CarMapper.xml"/>  
    </mappers>  
</configuration>
@Test  
public void testDataSource() throws Exception{  
    // 准备数据  
    Car car = new Car();  
    car.setCarNum("133");  
    car.setBrand("丰田霸道");  
    car.setGuidePrice(50.3);  
    car.setProduceTime("2020-01-10");  
    car.setCarType("燃油车");  
    // 获取SqlSessionFactory对象  
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder  
            .build(Resources.getResourceAsStream("mybatis-config3.xml"));  
    // 获取SqlSession对象  
    SqlSession sqlSession = sqlSessionFactory.openSession(true);  
    // 执行SQL  
    int count = sqlSession.insert("insertCar", car);  
    System.out.println("插入了几条记录:" + count);  
    // 关闭会话  
    sqlSession.close();  
}

当type是UNPOOLED,控制台输出:
image.png

修改配置文件mybatis-config3.xml中的配置:

<dataSource type="POOLED">

Java测试程序不需要修改,直接执行,看控制台输出:
image.png

通过测试得出:UNPOOLED不会使用连接池,每一次都会新建JDBC连接对象。POOLED会使用数据库连接池。【这个连接池是mybatis自己实现的。】

<dataSource type="JNDI">

JNDI的方式:表示对接JNDI服务器中的连接池。这种方式给了我们可以使用第三方连接池的接口。如果想使用dbcp、c3p0、druid(德鲁伊)等,需要使用这种方式。

type="POOLED"的时候,它的属性有哪些?

<?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>  
    <environments default="dev">  
        <environment id="dev">  
            <transactionManager type="JDBC"/>  
            <dataSource type="POOLED">  
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>  
                <property name="url" value="jdbc:mysql://localhost:3306/rainsoul"/>  
                <property name="username" value="root"/>  
                <property name="password" value="root"/>  
                <!--最大连接数-->  
                <property name="poolMaximumActiveConnections" value="3"/>  
                <!--这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接  
                (避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。-->  
                <property name="poolTimeToWait" value="20000"/>  
                <!--强行回归池的时间-->  
                <property name="poolMaximumCheckoutTime" value="20000"/>  
                <!--最多空闲数量-->  
                <property name="poolMaximumIdleConnections" value="1"/>  
            </dataSource>  
        </environment>  
    </environments>  
    <mappers>  
        <mapper resource="CarMapper.xml"/>  
    </mappers>  
</configuration>

poolMaximumActiveConnections:最大的活动的连接数量。默认值10
poolMaximumIdleConnections:最大的空闲连接数量。默认值5
poolMaximumCheckoutTime:强行回归池的时间。默认值20秒。
poolTimeToWait:当无法获取到空闲连接时,每隔20秒打印一次日志,避免因代码配置有误,导致傻等。(时长是可以配置的)
当然,还有其他属性。对于连接池来说,以上几个属性比较重要。 最大的活动的连接数量就是连接池连接数量的上限。默认值10,如果有10个请求正在使用这10个连接,第11个请求只能等待空闲连接。最大的空闲连接数量。默认值5,如何已经有了5个空闲连接,当第6个连接要空闲下来的时候,连接池会选择关闭该连接对象。来减少数据库的开销。需要根据系统的并发情况,来合理调整连接池最大连接数以及最多空闲数量。充分发挥数据库连接池的性能。【可以根据实际情况进行测试,然后调整一个合理的数量。】

在以上配置的基础之上,可以编写java程序测试:

@Test  
public void testPool() throws Exception{  
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder  
            .build(Resources.getResourceAsStream("mybatis-config3.xml"));  
    for (int i = 0; i < 4; i++) {  
        SqlSession sqlSession = sqlSessionFactory.openSession();  
        Object selectCarByCarNum = sqlSession.selectOne("selectCarByCarNum");  
    }  
}

image.png

5.properties

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

jdbc.driver=com.mysql.cj.jdbc.Driver  
jdbc.url=jdbc:mysql://localhost:3306/rainsoul

在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 resource="jdbc.properties">  
        <property name="jdbc.username" value="root"/>  
        <property name="jdbc.password" value="root"/>  
    </properties>  
  
    <environments default="dev">  
        <environment id="dev">  
            <transactionManager type="JDBC"/>  
            <dataSource type="POOLED">  
                <!--${key}使用-->  
                <property name="driver" value="${jdbc.driver}"/>  
                <property name="url" value="${jdbc.url}"/>  
                <property name="username" value="${jdbc.username}"/>  
                <property name="password" value="${jdbc.password}"/>  
            </dataSource>  
        </environment>  
    </environments>  
    <mappers>  
        <mapper resource="CarMapper.xml"/>  
    </mappers>  
</configuration>

编写Java程序进行测试:

@Test  
public void testProperties() throws Exception{  
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder  
            .build(Resources.getResourceAsStream("mybatis-config4.xml"));  
    SqlSession sqlSession = sqlSessionFactory.openSession();  
    Object car = sqlSession.selectOne("selectCarByCarNum");  
    System.out.println(car);  
}

properties两个属性:

  • resource:这个属性从类的根路径下开始加载。【常用的。】
  • url:从指定的url加载,假设文件放在d:/jdbc.properties,这个url可以写成:file:///d:/jdbc.properties。注意是三个斜杠哦。
    注意:如果不知道mybatis-config.xml文件中标签的编写顺序的话,可以有两种方式知道它的顺序:
    第一种方式:查看dtd约束文件。
    第二种方式:通过idea的报错提示信息。【一般采用这种方式】

6.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>

mapper还有其他的指定方式。

7.在WEB中应用MyBatis(使用MVC架构模式)

7.1第一步:环境搭建:

-- auto-generated definition  
create table t_act  
(  
    id      bigint auto_increment  
        primary key,  
    actno   varchar(255)   null,  
    balance decimal(15, 2) null  
);

添加数据:
image.png

IDEA中创建Maven WEB应用。 IDEA配置Tomcat,Tomcat使用10+版本。并部署应用到tomcat。
引入的依赖包括:mybatis,mysql驱动,junit,logback,servlet。 删除index.jsp文件,因为我们这个项目不使用JSP。只使用html。确定pom.xml文件中的打包方式是war包。引入相关配置文件,放到resources目录下(全部放到类的根路径下)。

<?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 resource="jdbc.properties"/>  
  
    <environments default="dev">  
        <environment id="dev">  
            <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}"/>  
            </dataSource>  
        </environment>  
    </environments>  
    <mappers>  
        <!--一定要注意这里的路径哦!!!-->  
        <mapper resource="AccountMapper.xml"/>  
    </mappers>  
</configuration>
jdbc.driver=com.mysql.cj.jdbc.Driver  
jdbc.url=jdbc:mysql://localhost:3306/rainsoul  
jdbc.username=root  
jdbc.password=root
<?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">  
  
<mapper namespace="account">  
 
</mapper>
<?xml version="1.0" encoding="UTF-8"?>  
  
<configuration debug="false">  
    <!-- 控制台输出 -->  
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">  
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">  
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->  
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>  
        </encoder>  
    </appender>  
    <!-- 按照每天生成日志文件 -->  
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">  
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
            <!--日志文件输出的文件名-->  
            <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>  
            <!--日志文件保留天数-->  
            <MaxHistory>30</MaxHistory>  
        </rollingPolicy>  
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">  
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->  
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>  
        </encoder>  
        <!--日志文件最大的大小-->  
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">  
            <MaxFileSize>100MB</MaxFileSize>  
        </triggeringPolicy>  
    </appender>  
  
    <!--mybatis log configure-->  
    <logger name="com.apache.ibatis" level="TRACE"/>  
    <logger name="java.sql.Connection" level="DEBUG"/>  
    <logger name="java.sql.Statement" level="DEBUG"/>  
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>  
  
    <!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->  
    <root level="DEBUG">  
        <appender-ref ref="STDOUT"/>  
        <appender-ref ref="FILE"/>  
    </root>  
  
</configuration>

7.2第二步:前端页面index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>银行账户转账</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f2f2f2;
            margin: 0;
            padding: 0;
        }

        form {
            width: 300px;
            margin: 50px auto;
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        input[type="text"], input[type="submit"] {
            width: 100%;
            padding: 10px;
            margin-bottom: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }

        input[type="submit"] {
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }

        input[type="submit"]:hover {
            background-color: #45a049;
        }
    </style>
</head>
<body>
<form action="/bank/transfer" method="post">
    <h2 style="text-align: center;">银行账户转账</h2>
    <label for="fromActno">转出账户:</label>
    <input type="text" id="fromActno" name="fromActno" required/><br>
    <label for="toActno">转入账户:</label>
    <input type="text" id="toActno" name="toActno" required/><br>
    <label for="money">转账金额:</label>
    <input type="text" id="money" name="money" required/><br>
    <input type="submit" value="转账"/>
</form>
</body>
</html>

效果如图:
image.png

7.3第三步:创建pojo包、service包、dao包、web包、utils包

image.png

SqlSessionUtil用之前的。

7.4定义pojo类:Account

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 Account() {  
    }  
  
    public Account(Long id, String actno, Double balance) {  
        this.id = id;  
        this.actno = actno;  
        this.balance = balance;  
    }  
  
	省略get,set方法
}

7.5编写AccountDao接口,以及AccountDaoImpl实现类

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

  • 转账前需要查询余额是否充足:selectByActno
  • 转账时要更新账户:update
package com.rainsoul.bank.dao;  
  
import com.rainsoul.bank.pojo.Account;  
  
public interface AccountDao {  
    /**  
     * 根据账号获取账户信息  
     */  
    Account selectByActno(String actno);  
  
    /**  
     * 更新账户信息  
     */  
    int update(Account act);  
}
package com.rainsoul.bank.dao.impl;  
  
import com.rainsoul.bank.dao.AccountDao;  
import com.rainsoul.bank.pojo.Account;  
import com.rainsoul.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 act = (Account)sqlSession.selectOne("selectByActno", actno);  
        sqlSession.close();  
        return act;  
    }  
  
    @Override  
    public int update(Account act) {  
        SqlSession sqlSession = SqlSessionUtil.openSession();  
        int count = sqlSession.update("update", act);  
        sqlSession.commit();  
        sqlSession.close();  
        return count;  
    }  
}

7.6第六步:AccountDaoImpl中编写了mybatis代码,需要编写SQL映射文件了

<?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">  
  
<mapper namespace="account">  
    <select id="selectByActno" resultType="com.rainsoul.bank.pojo.Account">  
        select * from t_act where actno = #{actno}    </select>  
    <update id="update">  
        update t_act set balance = #{balance} where actno = #{actno}    </update>  
</mapper>

7.7编写AccountService接口以及AccountServiceImpl

package com.rainsoul.bank.exception;  
  
public class MoneyNotEnoughException extends Exception {  
    public MoneyNotEnoughException() {  
    }  
  
    public MoneyNotEnoughException(String msg) {  
        super(msg);  
    }  
}
package com.rainsoul.bank.exception;  
  
public class AppException extends Exception {  
    public AppException() {  
    }  
  
    public AppException(String msg) {  
        super(msg);  
    }  
}
package com.rainsoul.bank.service;  
  
import com.rainsoul.bank.exception.AppException;  
import com.rainsoul.bank.exception.MoneyNotEnoughException;  
  
public interface AccountService {  
  
    void transfer(String fromActno, String toActno, double money)  
            throws MoneyNotEnoughException, AppException;  
}
package com.rainsoul.bank.service.impl;  
  
import com.rainsoul.bank.dao.AccountDao;  
import com.rainsoul.bank.dao.impl.AccountDaoImpl;  
import com.rainsoul.bank.exception.AppException;  
import com.rainsoul.bank.exception.MoneyNotEnoughException;  
import com.rainsoul.bank.pojo.Account;  
import com.rainsoul.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, AppException {  
        // 查询转出账户的余额  
        Account fromAct = accountDao.selectByActno(fromActno);  
        if (fromAct.getBalance() < money) {  
            throw new MoneyNotEnoughException("对不起,您的余额不足。");  
        }  
        try {  
            // 程序如果执行到这里说明余额充足  
            // 修改账户余额  
            Account toAct = accountDao.selectByActno(toActno);  
            fromAct.setBalance(fromAct.getBalance() - money);  
            toAct.setBalance(toAct.getBalance() + money);  
            // 更新数据库  
            accountDao.update(fromAct);  
            accountDao.update(toAct);  
        } catch (Exception e) {  
            throw new AppException("转账失败,未知原因!");  
        }  
    }  
}

7.8编写AccountController

package com.rainsoul.bank.web;  
  
import com.rainsoul.bank.exception.AppException;  
import com.rainsoul.bank.exception.MoneyNotEnoughException;  
import com.rainsoul.bank.service.AccountService;  
import com.rainsoul.bank.service.impl.AccountServiceImpl;  
import jakarta.servlet.ServletException;  
import jakarta.servlet.annotation.WebServlet;  
import jakarta.servlet.http.HttpServlet;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
  
import java.io.IOException;  
import java.io.PrintWriter;  
  
@WebServlet("/transfer")  
public class AccountServlet extends HttpServlet {  
  
    private AccountService accountService = new AccountServiceImpl();  
  
    @Override  
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        // 获取响应流  
        response.setContentType("text/html;charset=UTF-8");  
        PrintWriter out = response.getWriter();  
        // 获取账户信息  
        String fromActno = request.getParameter("fromActno");  
        String toActno = request.getParameter("toActno");  
        double money = Integer.parseInt(request.getParameter("money"));  
        // 调用业务方法完成转账  
        try {  
            accountService.transfer(fromActno, toActno, money);  
            out.print("<h1>转账成功!!!</h1>");  
        } catch (MoneyNotEnoughException e) {  
            out.print(e.getMessage());  
        } catch (AppException e) {  
            out.print(e.getMessage());  
        }  
    }  
}

测试:
image.png
image.png

8.MyBatis对象作用域以及事务问题

8.1.SqlSessionFactoryBuilder

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

8.2.SqlSessionFactory

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

8.3SqlSession

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

8.4事务问题

在之前的转账业务中,更新了两个账户,我们需要保证它们的同时成功或同时失败,这个时候就需要使用事务机制,在transfer方法开始执行时开启事务,直到两个更新都成功之后,再提交事务,我们尝试将transfer方法进行如下修改:

@Override  
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {  
    // 查询转出账户的余额  
    Account fromAct = accountDao.selectByActno(fromActno);  
    if (fromAct.getBalance() < money) {  
        throw new MoneyNotEnoughException("对不起,您的余额不足。");  
    }  
    try {  
        // 程序如果执行到这里说明余额充足  
        // 修改账户余额  
        Account toAct = accountDao.selectByActno(toActno);  
        fromAct.setBalance(fromAct.getBalance() - money);  
        toAct.setBalance(toAct.getBalance() + money);  
        // 更新数据库(添加事务)  
        SqlSession sqlSession = SqlSessionUtil.openSession();  
        accountDao.update(fromAct);  
        // 模拟异常  
        String s = null;  
        s.toString();  
        accountDao.update(toAct);  
        sqlSession.commit();  
        sqlSession.close();  
    } catch (Exception e) {  
        throw new AppException("转账失败,未知原因!");  
    }  
}

结果:
image.png

傻眼了吧!!!事务出问题了,转账失败了,钱仍然是少了1万。这是什么原因呢?主要是因为service和dao中使用的SqlSession对象不是同一个。怎么办?为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到ThreadLocal当中。修改SqlSessionUtil工具类:

package com.rainsoul.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;  
  
public class SqlSessionUtil {  
    private static SqlSessionFactory sqlSessionFactory;  
  
    /**  
     * 类加载时初始化sqlSessionFactory对象  
     */  
    static {  
        try {  
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  
            sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();  
  
    /**  
     * 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。  
     *  
     * @return 新的会话对象  
     */  
    public static SqlSession openSession() {  
        SqlSession sqlSession = local.get();  
        if (sqlSession == null) {  
            sqlSession = sqlSessionFactory.openSession();  
            local.set(sqlSession);  
        }  
        return sqlSession;  
    }  
  
    /**  
     * 关闭SqlSession对象  
     * @param sqlSession  
     */  
    public static void close(SqlSession sqlSession){  
        if (sqlSession != null) {  
            sqlSession.close();  
        }  
        local.remove();  
    }  
}

修改dao中的方法:AccountDaoImpl中所有方法中的提交commit和关闭close代码全部删除。修改service中的方法:
image.png

9.分析当前程序存在的问题

我们来看一下DaoImpl的代码

package com.rainsoul.bank.dao.impl;  
  
import com.rainsoul.bank.dao.AccountDao;  
import com.rainsoul.bank.pojo.Account;  
import com.rainsoul.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 act = (Account) sqlSession.selectOne("selectByActno", actno);  
//        sqlSession.close();  
        return act;  
    }  
  
    @Override  
    public int update(Account act) {  
        SqlSession sqlSession = SqlSessionUtil.openSession();  
        int count = sqlSession.update("update", act);  
//        sqlSession.commit();  
//        sqlSession.close();  
        return count;  
    }  
}

我们不难发现,这个dao实现类中的方法代码很固定,基本上就是一行代码,通过SqlSession对象调用insert、delete、update、select等方法,这个类中的方法没有任何业务逻辑,既然是这样,这个类我们能不能动态的生成,以后可以不写这个类吗?答案:可以。

  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值