在 Wed 中應用 MyBatis(同時使用MVC架構模式,以及ThreadLocal 事務控制)

Rainbow-Sea發表於2024-06-12

1. 在 Wed 中應用 MyBatis(同時使用MVC架構模式,以及ThreadLocal 事務控制)

@

目錄
  • 1. 在 Wed 中應用 MyBatis(同時使用MVC架構模式,以及ThreadLocal 事務控制)
  • 2. 實現步驟:
    • 1. 第一步:環境搭建
    • 2. 第二步:前端頁面 index.html
    • 3. 第三步:建立pojo包、service包、dao包、web包、utils包,exceptions包
    • 4. 第四步:編寫 utils 包下的,獲取 MyBatis,SqlSesion 連線的工具類
    • 5. 第五步:定義pojo類:Account
    • 6. 第六步:編寫AccountDao介面,以及AccountDaoImpl實現類
    • 7. 第七步:AccountDaoImpl 中編寫了mybatis 程式碼,需要編寫SQL對映檔案了
    • 8. 第八步:編寫AccountService介面以及AccountServiceImpl
    • 9. 第九步:編寫 自定義 Exception 異常
    • 10. 第十步:編寫AccountController
    • 11. 第十一步:執行測試:
    • 2.1 補充說明:事務上的處理
  • 3. MyBatis核心物件的作用域
    • 3.1 SqlSessionFactoryBuilder
    • 3.2 SqlSessionFactory
    • 3.3 SqlSession
  • 4. 總結:
  • 5. 最後:


在 Web 中應用 MyBatis ,同時使用 MVC 架構模式,以及對應的 ThreadLocal 事務控制。

實現功能,銀行賬戶的轉賬功能,同時進行事務上的處理。

需求描述:

實際簡單的轉賬操作:

在這裡插入圖片描述

資料庫表的設計和準備資料

在這裡插入圖片描述

在這裡插入圖片描述

2. 實現步驟:

這裡說明一下,開發可以

  • 從後往前
  • 也可以,從前往後

二者沒有太大區別,你認為哪個方向更好編寫,便按照哪個方向即可,我個人比較習慣從前往後,所以這裡我就從前往後了。

1. 第一步:環境搭建

IDEA中建立Maven WEB應用

注意:這裡的 Archetype : 選擇org.apache.maven.archetypes:maven-archetype-webapp 不要選錯了。

在這裡插入圖片描述

IDEA配置Tomcat,這裡Tomcat使用10+版本。並部署應用到tomcat。

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

預設建立的maven web應用沒有 java resources目錄。

在這裡插入圖片描述

一般會自動新增上,如果沒有的話,有兩種手動新增上的方式:

  • 第一種就是:直接在 IDEA 當的 main 目錄下,新建

在這裡插入圖片描述

  • 第二種修改:修改maven-archetype-webapp-1.4.jar中的配置檔案

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

這裡自動生成的:web.xml 檔案的版本較低,內容有點不太合適,我們可以從 tomcat10 的樣例檔案中複製,然後修改

如下是:tomcat 10 當中的樣例:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                      https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0"
         metadata-complete="true">

</web-app>
  • 刪除 index.jsp檔案,因為我們這個專案不使用JSP。只使用 html。

  • 確定 pom.xml 檔案中的打包方式是 war 包。

  • 引入相關依賴

    • 編譯器版本修改為 17
    • 引入的依賴包括:mybatis,mysql驅動,junit,logback,servlet。

在這裡插入圖片描述

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.rainbowsea</groupId>
  <artifactId>mybatis-004-web</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>mybatis-004-web Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!--      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>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>5.0.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <finalName>mybatis-004-web</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

  • 引入相關配置檔案,放到resources目錄下(全部放到類的根路徑下)

    • mybatis-config.xml
    • AccountMapper.xml
    • logback.xml
    • jdbc.properties
  • 在這裡插入圖片描述

  • logback.xml logbak 日誌框架資訊

  • 在這裡插入圖片描述

  • <?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>
    
        <!--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>
    
  • AccountMapper.xml 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">
    <!--namespace先隨意寫一個-->
    <mapper namespace="account">
    	<select id="selectByActno" resultType="com.rianbowsea.bank.pojo.Account">
    		select * from t_act where actno = #{actno}
    	</select>
    <!--#{pojo的屬性名}-->
    
    	<update id="updateByActno">
    		update t_act set balance = #{balance} where actno = #{actno}
    	</update>
    
    </mapper>
    
  • jdb.properties 資料庫連線資訊的配置檔案

  • jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/mybatis
    jdbc.username=root
    jdbc.password=MySQL123
    
  • mybatis-config.xml 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>
        <!--resource , 一定是從類路徑下開始查詢資源-->
        <properties resource="jdbc.properties"></properties>
        <environments default="mybatis">
            <environment id="mybatis">
                <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>
    

2. 第二步:前端頁面 index.html

在Tomcat當中 ,index.html 預設就是開始頁面,主頁的。

在這裡插入圖片描述

<!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>

3. 第三步:建立pojo包、service包、dao包、web包、utils包,exceptions包

在這裡插入圖片描述

4. 第四步:編寫 utils 包下的,獲取 MyBatis,SqlSesion 連線的工具類

在這裡插入圖片描述

package com.rianbowsea.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;

public class SqlSessionUtil {
    // 工具類的構造方法一般都是私有話化的
    // 工具類中所有的方法都是靜態的,直接類名即可呼叫,不需要 new 物件
    // 為了防止new物件,構造方法私有化。

    private SqlSessionUtil() {

    }


    private static SqlSessionFactory sessionFactory = null;

    // 靜態程式碼塊,類載入時執行
    // SqlSessionUtil 工具類在進行第一次載入的時候,解析mybatis-config.xml 檔案,建立SqlSessionFactory物件。
    static {
        // 獲取到  SqlSessionFactoryBuilder 物件
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

        // 獲取到SqlSessionFactory 物件
        // SQlsessionFactory物件,一個SqlSessionFactory對應一個 environment, 一個environment通常是一個資料庫
        try {
            sessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }


    // 全域性的,伺服器級別的,一個伺服器當中定義一個即可
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();

    /**
     * 獲取會話物件
     *
     * @return SqlSession
     */
    public static SqlSession openSession() {
        // 先從 ThreadLocal 當中獲取,獲取到 SqlSession 物件
        SqlSession sqlSession = local.get();

        if (null == sqlSession) {
            // ThreadLocat 沒有就, 建立一個
            sqlSession = sessionFactory.openSession();
            // 同時將其設定到 ThreadLocal容器當中,將SqlSession物件繫結到當前執行緒上
            local.set(sqlSession);
        }

        return sqlSession;
    }


    /**
     * 關閉SqlSession 物件(從當前執行緒中移除SqlSession 物件)
     * @param sqlSession
     */
    public static void close(SqlSession sqlSession) {
        if(sqlSession != null) {
            // 1.先將其關閉
            sqlSession.close();
            // 2. 再將其當前執行緒移除ThreadLocal 當前執行緒外面,防止被其他執行緒拿到整個沒用的執行緒
            local.remove();
            /*
            注意:移除SqlSession 物件和當前執行緒的繫結關係
            因為Tomcat 伺服器是支援執行緒池的,也就是說,用過的先吃物件t1,可能下一I此還會使用整個t1(已經關閉,沒用的)執行緒。
             */
        }
    }
}

5. 第五步:定義pojo類:Account

對於 pojo 當中的類,一定要有 set 和 get 方法,以及無引數構造方法,不然,大部分的框架是無法透過反射機制,進行操作的,從而出現錯誤的。

在這裡插入圖片描述

package com.rianbowsea.bank.pojo;


/**
 * 賬戶類,封裝賬戶資料
 */
public class Account {
    private Long id;
    private String actno;
    private Double balance;


    public Account() {
    }

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

6. 第六步:編寫AccountDao介面,以及AccountDaoImpl實現類

分析dao中至少要提供幾個方法,才能完成轉賬:

  • 轉賬前需要查詢餘額是否充足:selectByActno
  • 轉賬時要更新賬戶:update

AccountDao

在這裡插入圖片描述

package com.rianbowsea.bank.dao;


import com.rianbowsea.bank.pojo.Account;

/**
 * 賬戶的DAO物件,負責t_act 表中資料的CRUD,一般一個表對應一個 DAO
 * 強調以下,DAO物件中的任何一個方法和業務不掛鉤,沒有任何業務邏輯在裡頭
 * DAo中的方法就是CRUD的,所以方法名大部分是:insertXxx,deletexxx,updatexxx,selectxxx
 */
public interface AccountDao {

    /**
     * 根據賬號查詢賬戶資訊
     * @param actno 賬號
     * @return 賬戶資訊
     */
    Account selectActno(String actno);


    /**
     * 更新賬戶資訊
     * @param account 被更新的賬戶資訊
     * @return 1表示更新成功,其他表示更新失敗
     */
    int updateByActno(Account account);


}

AccountDaoImpl

在這裡插入圖片描述

package com.rianbowsea.bank.dao.impl;

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

public class AccountDaoImpl implements AccountDao {
    private SqlSession sqlSession = SqlSessionUtil.openSession();

    @Override
    public Account selectActno(String actno) {
        Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
        // 注意:事務的控制,都是放在業務層的,不是放在持久層DAo,更不放在utils工具層
        //sqlSession.close();
        return account;
    }

    @Override
    public int updateByActno(Account account) {


        int count = sqlSession.update("account.updateByActno", account);
        // 注意:事務的控制,都是放在業務層的,不是放在持久層DAo,更不放在utils工具層
        //sqlSession.commit();  // 提交資料
        //sqlSession.close();
        return count;
    }
}

7. 第七步:AccountDaoImpl 中編寫了mybatis 程式碼,需要編寫SQL對映檔案了

AccountMapper.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="account">
	<select id="selectByActno" resultType="com.rianbowsea.bank.pojo.Account">
		select * from t_act where actno = #{actno}
	</select>
<!--#{pojo的屬性名}-->

	<update id="updateByActno">
		update t_act set balance = #{balance} where actno = #{actno}
	</update>

</mapper>

8. 第八步:編寫AccountService介面以及AccountServiceImpl

AccountService

在這裡插入圖片描述

package com.rianbowsea.bank.service;


import com.rianbowsea.bank.exceptions.MoneyNotEnoughException;
import com.rianbowsea.bank.exceptions.TransferException;

/**
 * 注意: 業務類當中的業務方法的名字在起名字的時候,最好見名知意,能夠體現出具體的業務是做什麼的
 * 賬戶業務類
 */
public interface AccountService {

    /**
     * 賬戶轉賬業務
     *
     * @param fromActno 轉出賬戶
     * @param toActno   轉入賬戶
     * @param money     轉賬金額
     */
    void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException;

}

AccountServiceImpl

在這裡插入圖片描述

package com.rianbowsea.bank.service.impl;

import com.rianbowsea.bank.dao.AccountDao;
import com.rianbowsea.bank.dao.impl.AccountDaoImpl;
import com.rianbowsea.bank.exceptions.MoneyNotEnoughException;
import com.rianbowsea.bank.exceptions.TransferException;
import com.rianbowsea.bank.pojo.Account;
import com.rianbowsea.bank.service.AccountService;
import com.rianbowsea.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.selectActno(fromActno);

        if (fromAct.getBalance() < money) {
            // 2.如果轉出賬戶餘額不足,提示使用者
            throw new MoneyNotEnoughException("對不起,餘額不足");
        }

        // 3. 如果轉出賬戶餘額充足,更新轉出賬戶的餘額(update)
        // 先在記憶體當中修改
        Account toACt = accountDao.selectActno(toActno);
        toACt.setBalance(toACt.getBalance() + money);
        fromAct.setBalance(fromAct.getBalance() - money);

        // 4. 更新轉入賬戶的餘額(update)
        int count = accountDao.updateByActno(toACt);
        count += accountDao.updateByActno(fromAct);

        // 模擬異常
        //String s = null;
        //s.toString();

        if(count !=2) {
            throw new TransferException("轉賬異常,未知原因");
        }

        // 注意:事務的控制,都是放在業務層的,不是放在持久層DAo,更不放在utils工具層
        sqlSession.commit(); // 提交資料
        sqlSession.close();
    }


}

9. 第九步:編寫 自定義 Exception 異常

MoneyNotEnoughException

在這裡插入圖片描述

package com.rianbowsea.bank.exceptions;


/**
 * 餘額不足異常
 */
public class MoneyNotEnoughException extends Exception{
    public MoneyNotEnoughException() {
    }

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

TransferException

在這裡插入圖片描述

package com.rianbowsea.bank.exceptions;


/**
 * 轉賬異常
 */
public class TransferException extends Exception{
    public TransferException() {
    }

    public TransferException(String message) {
        super(message);
    }
}

10. 第十步:編寫AccountController

AccountController

在這裡插入圖片描述

package com.rianbowsea.bank.web;

import com.rianbowsea.bank.exceptions.MoneyNotEnoughException;
import com.rianbowsea.bank.exceptions.TransferException;
import com.rianbowsea.bank.service.AccountService;
import com.rianbowsea.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;


@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
    // 為了讓整個物件在其他方法中可以用,宣告為例項變數
    private AccountService accountService = new AccountServiceImpl();

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {

        // 獲取表單資料
        String fromActno = request.getParameter("fromActno");
        String toActno = request.getParameter("toActno");
        double money = Double.parseDouble(request.getParameter("money"));

        // 轉賬業務
        try {
            // 呼叫service的轉賬方法完成轉賬,(呼叫業務層)
            accountService.transfer(fromActno,toActno,money);
            // 程式能夠走到這裡,表示轉賬一定成功了
            // 呼叫View完成展示結果
            response.sendRedirect(request.getContextPath()+"/success.html");
        } catch (MoneyNotEnoughException e) {
            response.sendRedirect(request.getContextPath()+"/error.html");
            throw new RuntimeException(e);
        } catch (TransferException e) {
            response.sendRedirect(request.getContextPath()+"/error2.html");
            throw new RuntimeException(e);
        } catch (NullPointerException e) {
            response.sendRedirect(request.getContextPath()+"/error2.html");
            throw new RuntimeException(e);
        }

    }
}

11. 第十一步:執行測試:

首先測試,沒有模擬異常,看是否可以轉賬成功。

在這裡插入圖片描述

模擬異常,看事務上的處理,是否成功,是否能成功回滾,是否會丟失資料

在這裡插入圖片描述

2.1 補充說明:事務上的處理

在之前的轉賬業務中,更新了兩個賬戶,我們需要保證它們的同時成功或同時失敗,這個時候就需要使用事務機制,在 transfer 方法開始執行時開啟事務,直到兩個更新都成功之後 ,為了保證service和dao中使用的SqlSession物件是同一個,可以將SqlSession物件存放到ThreadLocal當中。

在這裡插入圖片描述

注意:移除SqlSession 物件和當前執行緒的繫結關係
因為Tomcat 伺服器是支援執行緒池的,也就是說,用過的先吃物件t1,可能下一I此還會使用整個t1(已經關閉,沒用的)執行緒。

注意:事務的控制,都是放在業務層的,不是放在持久層DAo,更不放在utils工具層 .

在這裡插入圖片描述

3. MyBatis核心物件的作用域

在這裡插入圖片描述

3.1 SqlSessionFactoryBuilder

在這裡插入圖片描述

這個類可以被例項化、使用和丟棄,一旦建立了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 例項的最佳作用域是方法作用域(也就是區域性方法變數)。 你可以重用 SqlSessionFactoryBuilder 來建立多個 SqlSessionFactory 例項,但最好還是不要一直保留著它,以保證所有的 XML 解析資源可以被釋放給更重要的事情。

3.2 SqlSessionFactory

在這裡插入圖片描述

SqlSessionFactory 一旦被建立就應該在應用的執行期間一直存在,沒有任何理由丟棄它或重新建立另一個例項。 使用 SqlSessionFactory 的最佳實踐是在應用執行期間不要重複建立多次,多次重建 SqlSessionFactory 被視為一種程式碼“壞習慣”。因此 SqlSessionFactory 的最佳作用域是應用作用域。 有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式。

3.3 SqlSession

在這裡插入圖片描述

每個執行緒都應該有它自己的 SqlSession 例項。SqlSession 的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域絕對不能將 SqlSession 例項的引用放在一個類的靜態域,甚至一個類的例項變數也不行也絕不能將 SqlSession 例項的引用放在任何型別的託管作用域中,比如 Servlet 框架中的 HttpSession。 如果你現在正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求相似的作用域中。 換句話說,每次收到 HTTP 請求,就可以開啟一個 SqlSession,返回一個響應後,就關閉它。 這個關閉操作很重要,為了確保每次都能執行關閉操作,你應該把這個關閉操作放到 finally 塊中。 下面的示例就是一個確保 SqlSession 關閉的標準模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的應用邏輯程式碼
}

4. 總結:

  1. 為了保證 service 和 dao 中使用的SqlSession物件是同一個,可以將SqlSession物件存放到ThreadLocal當中。

在這裡插入圖片描述

  1. 注意:移除SqlSession 物件和當前執行緒的繫結關係
    因為Tomcat 伺服器是支援執行緒池的,也就是說,用過的先吃物件t1,可能下一I此還會使用整個t1(已經關閉,沒用的)執行緒。

在這裡插入圖片描述

  1. 注意:事務的控制,都是放在業務層的,不是放在持久層DAo,更不放在utils工具層 .

  2. SqlSessionFactoryBuilder: 這個類可以被例項化、使用和丟棄,一旦建立了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 例項的最佳作用域是方法作用域(也就是區域性方法變數)。

  3. SqlSessionFactory 一旦被建立就應該在應用的執行期間一直存在,沒有任何理由丟棄它或重新建立另一個例項。

  4. SqlSession 它的最佳的作用域是請求或方法作用域**。 絕對不能將 SqlSession 例項的引用放在一個類的靜態域,甚至一個類的例項變數也不行。 **也絕不能將 SqlSession 例項的引用放在任何型別的託管作用域中,比如 Servlet 框架中的 HttpSession。 如果你現在正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求相似的作用域中。

5. 最後:

“在這個最後的篇章中,我要表達我對每一位讀者的感激之情。你們的關注和回覆是我創作的動力源泉,我從你們身上吸取了無盡的靈感與勇氣。我會將你們的鼓勵留在心底,繼續在其他的領域奮鬥。感謝你們,我們總會在某個時刻再次相遇。”

在這裡插入圖片描述

相關文章