Live2D
复制代码

04-项目和Mybatis

1、项目的三层架构

包括:界面层(User Interface Layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data Access Layer)
界面层:和用户打交道,接收用户请求参数,显示处理结果(包含如jsp、html、servlet等)

业务逻辑层:接收界面层传递的数据,计算逻辑,调用数据库,获取数据

数据访问层:访问数据库,执行对数据的查询,修改,删除等。

--三层中在项目目录中对应创建的包:
界面层(Controller层):controller包,servlet

业务逻辑层(Service层):service包,XXXService类

数据访问层(Dao层):dao包,XXXDao类

--三层中java类的交互
用户使用界面层->业务逻辑层->数据访问层(持久层)->数据库(MySQL)
--具体实现过程:
	用户在界面发起请求,界面层接收请求数据传递给业务逻辑层,
	逻辑层根据请求进行业务处理,需要使用到数据库,
	然后访问到持久层,持久层连接数据库,数据库根据需求返回数据给持久层,
	持久层又把数据给业务逻辑层,业务逻辑层拿到数据进行加工,最后把结果通过界面层展示给用户
	
--三层需要的对应处理框架
界面层(Controller层)-servlet-springMVC(框架)

业务逻辑层(Service层)-Service类-spring(框架)

数据访问层(Dao层)-Dao类-MyBatis(框架)

2、Maven项目使用Mybatis

2.1 实现步骤

在maven项目中使用mybatis实现查询数据库库中的一张表
步骤:
	1、在对应目录中创建一个空工程:idea-maven-mybatis
	2、添加新的maven模块:a-maven-mybatis01,保存在空工程的目录下
	3、在pom文件中添加mybatis的依赖坐标和mysql的依赖坐标
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.2</version>
    </dependency>
    
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
	</dependency>

    4、创建实体类com.studymyself.entity.User
    --作用是mybatis用来创建对象保存查询结果集中表中的一行数据
    
    5、创建持久层的dao接口com.studymyself.dao.UserDao其中定义操作数据库的方法
    --mybatis是在持久层起作用的
    
    6、在dao包下创建sql映射文件--一般一个表对应一个映射文件,文件名和接口名一样
    
    由于该sql映射文件不在resources目录中,所以要在pom文件中配置资源位置的插件信息,使该文件编译时拷贝到
    target对应目录中
   <build>
    <resources>
      <resource>
        <!--表示编译时在src/main/java目录下的-->
        <directory>src/main/java</directory>
        <!--所有.properties、.xml类型的文件可以被扫描到-->
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <!--filtering的值false表示不启用过滤器,上面include已经是过滤操作了-->
        <filtering>false</filtering>
       </resource>
     </resources>
   </build>
    7、在resources目录中创建mybatis核心目录文件
    8、在resources目录下创建保存数据库连接信息的属性文件--jdbc.properties
    9、创建使用的mybatis类,通过mybatis访问数据库的代码。--在studymyself包下创建MyApp.java
    

2.2 错误分析

--首先我们要知道的是什么叫项目的类的根路径:
maven项目中,点击右边的maven会出现maven窗口,双击其中的compile,开始项目的编译,而后生成一个target文件目录。
打开target里面是一个classes目录,存放的是编译后的class文件和resources目录中的资源配置文件。
当我们不是maven项目时,直接运行项目,也是先编译后运行,也会生成target目录,和上面一样。

所以运行时JVM是到classes目录中找class文件和配置资源文件来运行的,
这样就可以理解为target\classes目录就是项目类的根路径,对应的是项目中的src目录,
在maven项目中src目录和src\main是相当于一样一个src目录,
所以maven项目中resources目录中的文件被拷贝到类的根路径下,
而java文件的class文件存放在原先一样的层次包中,
只不过是根目录换了个名字而已,项目的目录结构还是没变,resources中被提取出来了。
就是说:
	maven项目中存放在resources目录中的资源配置文件,编译后存到的是类根路径下,
	所以上面第八步中代码的获取mybatis的核心配置文件时该文件的路径只需填文件名加文件类型就可以了。
	而sql映射文件通过资源插件的配置拷贝到的是target/classes/com/studymyself/dao目录中,
	mybatis是从根路径中扫描文件,所以在mybatis核心配置文件中配置sql映射文件的位置信息就是这样:	
	com/studymyself/dao/UserDao.xml
	--注意是左斜杠,不是点,还要保证resources目录一定要是资源文件夹
	有时候我们的项目配置都没有问题,但生成的target目录中不一定有相关的文件,
	我们就得clean,然后再编译。
	或者直接Rebuild Project。
	或者手工复制资源配置文件到target对应目录中。
	或者File->Invalidata Caches/Restart,表示删除idea之前项目的缓存,重启idea

2.3 在上面的基础上增加添加数据的方法测试

2.4 首先在UserDao增加添加数据的方法

package com.studymyself.dao;

import com.studymyself.entity.User;

import java.util.List;

//接口操作t_user表
public interface UserDao {

    //该方法查询t_user表中所有数据
    public List<User> getAll();

    //添加数据的方法,要和映射文件中sql语句的id一致,namespace的值也要是本接口的全限定名
    //才能将方法和sql映射文件中的某个id的sql语句绑定,而方法中传递的参数就和sql语句的parameterType属性
    //的作用一样
    public int addUser(User user);

}

2.5 添加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">
        <!--命名空间属性:里面的值是dao接口的全限定名,虽然可以自定义字符串,但不能这样用-->
        <!--作用是区分有多个sql映射文件时,下面sql语句的唯一标识id可能重复,
            这时就用接口全限定名.id 来区别执行哪个映射文件的同id的sql语句了
            com.studymyself.dao.UserDao.getAll
        -->
<mapper namespace="com.studymyself.dao.UserDao">

    <!--id中的值是这条sql语句在本映射文件中的唯一标识
        而com.studymyself.dao.UserDao.getAll是这条sql语句在本项目中的唯一标识
    -->
    <select id="getAll" resultType="com.studymyself.entity.User">
        select
            id,loginName,loginPwd,realName
        from
            t_user
    </select>

    <!--下面需要传参数,但是我们并没有设置parameterType属性却仍然可以执行添加数据的操作
        原因是namespace属性绑定的UserDao接口中添加数据的方法addUser需要的参数是User对象
        本句sql语句的id和接口中的方法名一致,所以下面占位符中传的值就是接口方法中传递
        的User对象中的值,也就不在多余设置parameterType属性为User类了,但是还是写上更好
    -->
    <insert id="addUser">
        insert into t_user
            (loginName,loginPwd,realName)
        values
            (#{loginName},#{loginPwd},#{realName})
    </insert>

</mapper>

2.6 核心配置文件添加一个全局配置,添加输出控制台日志的配置

<?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"/>

    <!--该属性的作用:控制mybatis全局行为,
        顺序要在上面的properties属性下面-->
    <settings>
        <!--设置mybatis输出日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--environments是环境配置,是复数说明下面可以配置多个环境
        所以default属性的值表示这个配置文件用下面的哪一套数据库配置信息
        这里使用development1
    -->
    <environments default="development1">
        <!--第一套数据库连接信息配置,唯一标识id叫development-->
        <environment id="development1">
            <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>
        <!--第二套数据库连接信息配置,唯一标识id叫development-->
        <environment id="development2">
            <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>
        <!--这里也可以添加多个sql映射文件,因为项目中不止一个表
            这就是映射文件中namespace属性的作用了-->
        <!-- <mapper resource="StudentDao.xml"/>-->
        <mapper resource="com/studymyself/dao/UserDao.xml"/>
    </mappers>
</configuration>

2.7 数据库信息的属性配置文件如下

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mysqlstudy
jdbc.username=root
jdbc.password=rong195302

2.8 pom文件

<?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.studymyself</groupId>
  <artifactId>a-maven-mybatis01</artifactId>
  <version>1.0-SNAPSHOT</version>

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

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

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.2</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
  </dependencies>

  <build>
    <resources>
      <resource>
        <!--表示编译时在src/main/java目录下的-->
        <directory>src/main/java</directory>
        <!--所有.properties、.xml类型的文件可以被扫描到-->
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <!--filtering的值false表示不启用过滤器,上面include已经是过滤操作了-->
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>
</project>

2.9 在test目录中新建mabatis测试类com.studymyself.TestMyApp

package com.studymyself;

import com.studymyself.entity.User;
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 TestMyApp {
    
    @Test
    public void testAddSql(){

        SqlSession sqlSession = null;

        try {
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
            sqlSession = sqlSessionFactory.openSession();

            //将UserDao.xml文件中namespace的值放进一个变量中
            String userId = "com.studymyself.dao.UserDao";

            //插入数据
            User user1 = new User();
            user1.setLoginName("12306");
            user1.setLoginPwd("sss123456789");
            user1.setRealName("铁路总局");
            int count = sqlSession.insert(userId+".addUser",user1);
            System.out.println("添加到数据库记录条数:"+count);

            sqlSession.commit();
        } catch (IOException e) {
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            e.printStackTrace();
        }finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }

    }

}
    

2.10 执行测试方法,控制台输出的日志和结果信息如下:

2.11 执行查询的输出信息如下

3、创建Mybatis的工具类

3.1 mybatis使用的对象的生命周期

--我们需要知道SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession三个对象的生命周期

--SqlSessionFactoryBuilder对象:
官方文档原话:
--这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 
因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,
但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
--这个类是用来创建获取SqlSessionFactory对象的,SqlSessionFactory对象一旦被创建SqlSessionFactoryBuilder就没有用了,可以被垃圾回收器回收销毁了。从官方例子中可以直接看出:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
从上面官方文档给的代码就知道:
	new出来的SqlSessionFactoryBuilder的对象用完build方法创建SqlSessionFactory对象后就被垃圾回收器回收了
	因为没有引用变量指向这个对象:SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
	而sqlSessionFactory指向的是SqlSessionFactory对象
	
--SqlSessionFactory对象:
官方文档原话:
--SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 
使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
--很明显该类对象是非常重要的,一个项目只能有一次创建,并且不能随意丢弃和重新创建另一个
如果放在一个工具类里的话,创建SqlSessionFactory对象的语句块需要放到静态语句块中,在项目加载时创建该对象,只执行一次。
SqlSessionFactory是一个接口:--实现类是 DefaultSqlSessionFactory
作用:获取SqlSession对象  SqlSession sqlSession =sqlSessionFactory.opensession();
--openSession()方法说明:
	openSession():方法中无需参数,获取sqlSession的时候会关闭自动提交事务机制
	openSession(boolean):参数是布尔类型,true表示获取sqlSession的时候会开启自动提交事务机制
									false表示获取sqlSession的时候会关闭自动提交事务机制
									
--SqlSession对象
SqlSessin也是一个接口,该接口中定义了许多操作数据的方法:实现类 DefaultSqlSession
官方文档原话:
--每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 
意思就是:
	SqlSession对象不是线程安全的,需要在方法的内部使用,即在一个方法中创建该对象(在执行sql语句前获取SqlSession对象:SqlSession session = sqlSessionFactory.openSession();),使用完毕后关闭sqlsession.close()
	每个线程都应该有它自己的 SqlSession 实例,所以一次请求对应一个SqlSession对象,就是说一个请求中不能被创建两个及以上的SqlSession对象,一个线程对应一次事务。不能被共享,就是说该对象不能定义为静态的。
	如果在工具类中,确保一次请求只能创建一个SqlSession对象,就是说无论怎么使用openSession方法,创建的都是同一个SqlSession对象,需要把该对象存到ThreadLocal对象local中(这是一个集合类对象),每次调用openSession方法前先判断local中是否有SqlSession对象(local.get()==null),为null,则执行local.set(sqlSession),然后sqlSession = local.get(),否者直接返回sqlSession。
	还需要注意的一点是,在写工具类的关闭SqlSession资源时,除了关闭sqlsession.close()外,还需要将执行local.remove()。理由是执行local.set(sqlSession)方法时,当前这个线程就绑定了local集合,因为Tomcat中存在一个"线程池",线程结束后是存到该线程池中的,线程结束后如果不移除local集合中的SqlSession对象,下次该线程可能被使用到的时候会继续使用上次的local,也就继续使用上一个SqlSession对象,但是由于上一个SqlSession对象执行了close方法,继续使用就会报错。
	

3.2 创建mybatis工具类

一个mybatis工具类:com.studymyself.utils.MyBatisUtil
package com.studymyself.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 MyBarisUtil {

    //需要在外边定义,下面的代码好使用
    private static SqlSessionFactory sqlSessionFactory = null;
    //SqlSessionFactory对象只能被创建一次
    static {
        //SqlSessionFactoryBuilder对象只是为了创建SqlSessionFactory对象,所以不需要引用指向
        //SqlSessionFactoryBuilder对象,代码执行完毕该对象就被回收了
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //定义一个集合存储SqlSession对象,确保一次事务(线程、请求)只创建一个
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();

    /**
     * 获取当前线程的SqlSession对象
     * @return
     */
    public static SqlSession getSqlSession(){
        //先获取local集合中的SqlSession
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession();
            local.set(sqlSession);
        }
        return sqlSession;
    }

    /**
     * 关闭资源以及移除集合中的sqlSession对象
     */
    public static void close(SqlSession sqlSession){

        if (sqlSession != null) {

            sqlSession.close();

            local.remove();

        }

    }
}
posted @ 2021-08-09 22:19  Milen-jie  阅读(42)  评论(0编辑  收藏  举报