mybatis的部署和加载详解

本文详细介绍了MyBatis框架的三层架构,包括API接口层、数据处理层和基础支撑层,并讲解了如何在Maven项目中引入MyBatis相关依赖。此外,还探讨了动态加载Class对象的方法,强调了newInstance()与new关键字创建对象的差异。最后,展示了使用PreparedStatement执行SQL查询的过程,以及MyBatis初始化时获取数据库连接、创建事务和Executor对象的步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plan Old Java Objects,普通的Java对象)映射成数据库中的记录。Mybatis的功能架构分为三层(图片借用了百度百科):

1)API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。

2)数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。

3)基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

如图所示:

 

以在maven管理的项目架构下创建工程为例,在pom.xml中引入相关的jar包:

<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.j1.mybatis</groupId>
  <artifactId>mybatis-demo</artifactId>
  <version>1.0.0-SNAPSHOT</version> 
      <parent>
        <groupId>cn.j1.parent</groupId>
        <artifactId>j1-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <dependencies>
        <!-- MySql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>
        <!-- Mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </dependency>
    </dependencies>
</project>

上述依赖是调用了java后台和MySQL数据库连接的封装依赖、通信和日志记录依赖以及mybatis封装依赖等,接下来创建一个简单的JDBC数据库连接代码
package com.j1.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class JdbcTest {
    public static void main(String[] args) {
        // 数据库连接
        Connection con = null;
        // 执行sql
        ResultSet res = null;
        // 封装sql
        PreparedStatement pre = null;
        // 加载驱动
        try {
            Class.forName("com.mysql.jdbc.Driver");
            // 创建连接
            // 创建连接
            String url = "jdbc:mysql://127.0.0.1:3306/mybatis";
            String username = "root";
            String password = "root";
            con = DriverManager.getConnection(url, username, password);

            // 获取PreparedStatement对象
            String sql = "select * from tb_user u  where  u.user_name=?";
            pre = con.prepareStatement(sql);

            // 封装查询的参数
            pre.setString(1, "wangwu");
            // 执行
            res = pre.executeQuery();
            // 打印结果集,
            while (res.next()) {
                System.out.println("username : " + res.getString("user_name"));
                System.out.println("name : " + res.getString("name"));
                System.out.println("age : " + res.getInt("age"));
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                // 释放资源
                if (pre != null) {
                    pre.close();
                }
                if (res != null) {
                    res.close();
                }
                if (con != null) {
                    con.close();
                }

            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

    }

}
class.forName()函数入参是一个类,目的是要求JVM查找并加载入参指定的类,也就是说JVM会执行该类的静态代码段。关于forName()方法,实际上这个方法和newInstance()方法一起对类进行实例化,二者结合等同于new关键字。如:A a = (A)Class.forName(“pacage.A”).newInstance(); 这和你 A a = new A(); 是一样的效果

动态加载和创建Class 对象,比如想根据用户输入的字符串来创建对象时需要用到:
String str = “用户输入的字符串” ;
Class t = Class.forName(str);
t.newInstance();

在初始化一个类,生成一个实例的时候,newInstance()方法和new关键字的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类而同时存在不同的对象创建方法这要考虑到软件的可伸缩、可扩展和可重用等软件设计思想

java工厂模式经常使用newInstance来创建实例,如:

class c = Class.forName(“Example”);
factory = (ExampleInterface)c.newInstance();

其中ExampleInterface是类Example的接口,上述代码又可以写成如下形式:

String className = “Example”;
class c = Class.forName(className);
factory = (ExampleInterface)c.newInstance();

进一步可以写成如下形式:

String className = readfromXMLConfig;//从xml 配置文件中获得字符串
class c = Class.forName(className);
factory = (ExampleInterface)c.newInstance();

这里需要说明的是,readfromXMLConfig并不是一个类,而是指通过读取XML文件中的类配置方法从而获取类名的抽象描述。由此,我们可以只要在目标文件中配置好继承了ExampleInterface接口的类,上述代码将会自动创建对应的实例。

JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载但是使用newInstance()方法的时候,就必须保证:
1、这个类已经加载;
2、这个类已经连接了。
而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载 java API的那个加载器。
现在可以看出,newInstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。

在Java开发特别是数据库开发中,经常会用到Class.forName( )这个方法。通过查询Java Documentation我们会发现使用Class.forName( )静态方法的目的是为了动态加载类。在加载完成后,一般还要调用Class下的newInstance( )静态方法来实例化对象以便操作。因此,单单使用Class.forName( )是动态加载类是没有用的,其最终目的是为了实例化对象。 即: Class.forName("")返回的是类 , Class.forName("").newInstance()返回的是object 

上述代码连接数据库的时候仅仅使用了forName()函数而没有使用newInstance(),甚至有的jdbc连接数据库的写法里是Class.forName(xxx.xx.xx);而有一 些:Class.forName(xxx.xx.xx).newInstance(),为什么会有这两种写法呢?
刚才提到,Class.forName("");的作用是要求JVM查找并加载指定的类,如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段。而在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBC Driver的 Driver类的代码都必须类似如下
  public class MyJDBCDriver implements Driver {
   static {
     DriverManager.registerDriver(new MyJDBCDriver());
  }
  }
 换言之,上述forName函数入参的Driver类在之前已经在JVM中实例化并注册了,既然在静态初始化器的中已经进行了注册,所以我们在使用JDBC时只需要Class.forName(XXX.XXX);就可以了。
贴出Proxool 连接池的静态初始化方法:
public class ProxoolDriver implements Driver {

    private static final Log LOG = LogFactory.getLog(ProxoolDriver.class);

    static {
        try {
            DriverManager.registerDriver(new ProxoolDriver());
        } catch (SQLException e) {
            System.out.println(e.toString());
        }
    }
}

getConnection()函数由继承javax.sql.DataSource 接口指定的连接数据库的函数,其功能封装在Driver类中,入参有url,username,password三个,其中URL包括有相应的JDBC驱动和数据库的存储地址,username和password分别对应连接数据库的用户名和密码。
prepareStatement()函数,其入参为代码中的sql语句,作用是对入参语句进行预编译,与其功能相似的函数还有createStatement()函数。二者同属connection类下的方法,只是二者实现方式不同,createStatement需要事先写好完整的sql语句作为入参,在创建时调用connection对象的方法createStatement()所返回的对象中的execute函数执行从而完成对sql语句的编译和执行,即createStatement对sql语句的预编译的入参口实际上是在createStatement创建对象中的execute方法处,如:
  1. String sql = "select * from users where  username= '"+username+"' and userpwd='"+userpwd+"'";  
  2. stmt = conn.createStatement();  
  3. rs = stmt.executeQuery(sql); 
而prepareStatement对sql的编译较为宽容,调用时将sql语句作为入参,只是入参时的sql语句较为宽容,可以用?代替sql语句字串组中的部分关键字串,当需要设定这些关键字串的值时,只需用setString等函数用自己的值对对应的?进行替换,预编译后再用execute方法执行,如:
  1. String sql = "select * from users where  username=? and userpwd=?";  
  2. pstmt = conn.prepareStatement(sql);  
  3. pstmt.setString(1, username);  
  4. pstmt.setString(2, userpwd);  
  5. rs = pstmt.executeQuery();  
其中,setString(v1,v2)有两个入参,入参v1是一个整型变量,代表输入的sql语句中的第v1个出现的?,v2是一个string变量,代表?应当填入的关键字。
因此prepareStatement方法在一定程度上避免了sql注入攻击。(这个真的不懂。。。。)
execute系列方法是执行sql语句并返回结果的方法,包括execute(),executeQuery()和executeUpdate(),execute函数接受返回多个结果集(union ResultSet)的sql语句;而executeQuery和executeUpdate函数只接受返回单个结果集(single ResultSet)的sql语句。
execute()方法返回一个boolean值,标明执行该语句是否返回一个ResultSet值。
executeUpdate()方法用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言)语句,例如 CREATE TABLE 和 DROP TABLE。INSERT、UPDATE 或 DELETE 语句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一个整数(int),指示受影响的行数(即更新计数)。对于 CREATE TABLE 或 DROP TABLE 等不操作行的语句,executeUpdate 的返回值总为零。
executeQuery()方法用于产生单个结果集(ResultSet)的语句,例如:被执行最多的SELECT 语句。 这个方法被用来执行 SELECT 语句,但也只能执行查询语句,执行后返回代表查询结果的ResultSet对象。
详细讲解可参看:http://blog.youkuaiyun.com/qq_20302155/article/details/73696246

实际上,上述代码便是绝大部分框架的数据库连接底层操作方式的一个比较典型的案例。然而开发中存在诸多问题,如:sql相关的连接、配置、执行等语句和用户名等参数都写在了代码之中,且对程序返回的结果的类型依赖手动判断,不便于代码的维护和复用,同时多次开关连接造成了极大的资源浪费。
因此,在解决上述问题时,有如下思路:sql配置、执行语句等放入外部连接,对参数的连接创立自动映射,并建立和维护连接池。

在部署Mybatis之前,其运行原理如:http://blog.youkuaiyun.com/u013631223/article/details/79225956参考,在此不多做赘述。
如下配置java bean文件User.java
package com.j1.jdbc.Model;

import java.util.Date;

public class User {
       private Long id;
        private String userName;
        private String password;
        private String name;
        private Integer age;
        private Boolean sex;
        private Date birthday;
        private Date created;
        private Date updated;
        public Long getId() {
            return id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
        public Boolean getSex() {
            return sex;
        }
        public void setSex(Boolean sex) {
            this.sex = sex;
        }
        public Date getBirthday() {
            return birthday;
        }
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
        public Date getCreated() {
            return created;
        }
        public void setCreated(Date created) {
            this.created = created;
        }
        public Date getUpdated() {
            return updated;
        }
        public void setUpdated(Date updated) {
            this.updated = updated;
        }
        @Override
        public String toString() {
            return "User [id=" + id + ", userName=" + userName + ", password="
                    + password + ", name=" + name + ", age=" + age + ", sex="
                    + sex + ", birthday=" + birthday + ", created=" + created
                    + ", updated=" + updated + "]";
        }

}
UserMapper.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">
  <mapper namespace="com.j1.user">
    <select id="queryUserByUserName" resultType="com.j1.jdbc.Model.User">
        SELECT *, user_name userName  FROM tb_user WHERE user_name = #{userName}
    </select>
    
</mapper>
其中,mapper下的namespace标签是用于绑定DAO接口的,即用于面向接口编程的配置,其值对应的是接口user所在的工程文件夹位置;select标签下的内容是和被存储在外部xml文件的sql语句,标签后的内容:id—>代表该sql语句在逻辑业务层的被调用代号,resultType—>代表该sql语句返回的结果集的类型,即对应结果集在逻辑层中存储java bean变量类的位置。因此,为正常使用该封装sql语句,在工程内还需创建一个接口文件user,并在接口中创建 一个 方法名为select id值, 返回值 类型 为id值对应的bean类或者类集合 的方法
package com.j1;

import org.apache.ibatis.annotations.Mapper;
import com.j1.jdbc.Model.User;

public interface user{
List<User> user(String userName)
}
其中,user的入参虽然没有必须的要求,但按习惯来讲还是最好和匹配select id下的sql语句中的关键字(#{}中的字段)名相符合。

jdbc.properties文件内容如下:
#jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.username=root
jdbc.password=root
全局l配置文件种关于mybatis的内容mybatis_config.xml配置如下
<?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"/>
    <settings>
        <!-- 开启驼峰自动映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <package name="com.j1.jdbc.Model"/>
    </typeAliases>
    <!-- 指定环境 -->
    <environments default="development">
        <environment id="development">
            <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>
<!-- 指定UserMapper.xml  -->
         <mapper resource="UserMapper.xml" /> 
    </mappers>
</configuration>
其中,typeAliases标签是用来设置执行文件的别名的,子标签package用于将路径文件夹(或包)下所有的类和接口批量设置别名,name为文件夹或包的路径, MyBatis默认的设置别名的方式就是去除类所在的包后的简单的类名,比如me.gacl.domain.User这个实体类的别名就会被设置成User。当然也可以用typeAlias标签单独为某个类设置别名, <typeAlias type="me.gacl.domain.User" alias="_User"/> 其中type值为类的路径,alias为路径类的别名。设置好别名后,这样,在Mapper中我们就不用每次配置都写类的全名了,但是namespace例外,namespace配置的时候必须写类的全名。

之后执行mybatis框架下的sql语句
package com.j1.mybatis;

import java.io.IOException;
import java.io.InputStream;

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 com.j1.jdbc.Model.User;

public class Mybatis {
public static void main(String[] args) {
    
    //讀取配置文件
      try {
            //配置文件
        String resource="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
         // 通过SqlSessionFactoryBuilder构建一个SqlSessionFactory
        SqlSessionFactory sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream);
        //System.out.println(sqlSessionFactory);
        SqlSession session=sqlSessionFactory.openSession();
        //System.out.println(session);
        User user=session.selectOne("com.j1.user.queryUserByUserName", "zhangsan");
        System.out.println(user);
        
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}    
}
       其中,Resources类提供了从路径读取中加载资源的多种易于使用的方法,主要用于:1、从类路径加载各种SQL Map配置文件;2、从类路径加载DAO manager配置文件;3、从类路径黏在各种properties文件。Resources中提供的加载方式有多种:1、对于简单的只读文本数据,加载为Reader,匹配方法为Reader getResourceAsReader(String resource);2、对于简单的只读二进制或文本数据,加载为Stream,方法为Stream getResourceAsStream(String resource);3、对于可读写的二进制或文本文件,加载为File,方法 File getResourceAsFile(String  resource);4、对于只读的配置属性文件,加载为Properties,方法为 Properties getResourceAsProperties(String resource);5、对于只读的通用资源,加载为URL,方法为 Url getResourceAsUrl(String resource)。上述方法在使用时,入参的resource应当为文件的全路径(以文件夹/文件名的形式,上面只写文件名的方式应该是写错了)其中,对于Resources类的方法也可以指定Classloader,也就是说各种方法有传参Classloader的重载形式,如 Properties   getResourceAsProperties(Classloader classloader,String resource)

       上段代码中,用stream方法将xml文件转化成二进制流存储在inputstream实例中,再由SqlSessionFactory实例读取配置(build函数)并创建数据库会话(openSession函数),SqlSessionFactory是个单个数据库映射关系经过编译后的内存镜像. SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象类获得,而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例。SqlSessionFactoryBuilder这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳范围是方法范围也就是局部方法变量)。每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心.同时SqlSessionFactory也是线程安全的, SqlSessionFactory一旦被创建,应该在应用执行期间都存在.在应用运行期间不要重复创建多次,建议使用单例模式.SqlSessionFactory是创建SqlSession的工厂. 
 
       SqlSession是MyBatis的关键对象,是执行持久化操作的独享,类似于JDBC中的Connection.它是应用程序与持久层之间执行交互操作的一个单线程对象,也是MyBatis执行持久化操作的关键对象.SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接,可以用SqlSession实例来直接执行被映射的SQL语句.每个线程都应该有它自己的SqlSession实例.SqlSession的实例不能被共享,同时SqlSession也是线程不安全的,绝对不能讲SqlSeesion实例的引用放在一个类的静态字段甚至是实例字段中.也绝不能将SqlSession实例的引用放在任何类型的管理范围中,比如Servlet当中的HttpSession对象中.使用完SqlSeesion之后关闭Session很重要,应该确保使用finally块来关闭它.

mybatis创建sqlsession经过了以下几个主要步骤:

1)       从核心配置文件mybatis-config.xml中获取Environment(这里面是数据源)

2)       Environment中取得DataSource

3)       Environment中取得TransactionFactory

4)       DataSource里获取数据库连接对象Connection

5)       在取得的数据库连接上创建事务对象Transaction

6)       创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的);

7)       创建sqlsession对象。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值