深入学习Mybatis-Mybatis入门


1. ORM思想

ORM(对象关系映射)

  • Object:JVM虚拟机中创建的对象

  • Relational:关于Object对象和数据

  • Mapping(映射):映射

一个简单的实例

Java虚拟机中创建的类

class User{
    private Integer id;
    private String name;
    private Integer age;
}

在数据库中的表结构

通过这种映射关系,可以将Java对象转化为数据库表中的一条记录

因为Mybatis是一个半自动的ORM,这是因为Mybatis中的SQL语句是需要程序员自己编写的。

2. 开发入门

  • 在Mybatis中有一个很重要的对象,这个对象是SqlSessionFactory
  • SqlSessionFactory需要通过XML文件进行读取

编写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>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

关于Mybatis的配置文件

Mybatis-config.xml:核心配置文件,用来配置连接数据库、事务管理器等的信息(一个)

xxxMapper.xml:专门用来编写SQL语句的配置文件(根据业务需求有0..n个)一张来说,一张表对应一个Mapper

修改以上基础配置信息即可,要注意的是resouces属性会自动从类的根路径下开始查找

接着使用Mybatis的类库编写mybatis程序,连接数据库,做CRUD

在Mybatis中负责执行SQL语句的对象叫做SqlSession,它是专门用来执行SQL的语句,是一个Java和数据库之间的一次会话,而如果想要获取SqlSession对象,需要先获取SqlSessionFactorty对象,通过SqlSessionFactory工厂来生产SqlSession对象,而如果想要获取SqlSessionFactory对象,则还要获取SqlSessionFactoryBuilder对象,然后调用build()方法即可

核心对象:

  • SqlSessionFactoryBuilder
  • SqlSessionFactory
  • SqlSession

编写Mapper文件

<?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="cn.edu.scau.cmi.zhangtingjie.orm.mybatis.StudentDAO">
    <insert id="addStudent">
        insert into student(id,name,tutor) values(?,?,?)
    </insert>
</mapper>
public static void testMybatis(){
    SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
    try {
        // 根据config.xml中的信息,解析得到对应的会话工厂
        // 所谓的会话工厂:就是基于xml信息,通过这些xml信息来给SqlSessionFactory中的一些信息赋值
        SqlSessionFactory sqlSessionFactory = factoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        //输入流,指向mybatis的核心配置文件
        SqlSession sqlSession = sqlSessionFactory.openSession();//获取会话对象
        int count = sqlSession.insert("addStudent");//执行对应的sql语句,不支持自动提交
        System.out.println("插入了"+count+"条语句");
        //需要在这里手动提交
        sqlSession.commit();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

一般情况下都是一个数据库对应一个sqlSession对象

Mybatis入门程序的细节问题

resouces:从类的根路径下开始加载

加载mybatis-config.xml的流需要手动关闭吗?

public SqlSessionFactory build(InputStream inputStream,
                               String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream,
                                                           environment, properties);
            var5 = this.build(parser.parse());
        } 
            try {
                if (inputStream != null) {
                    inputStream.close();//可以看到这个方法帮我们自动关闭了
                }
            }
}

因此,由于这个方法会帮我们关闭流,因此流就不需要我们手动关闭了,因此,你也可以认为这个流是一次性的,运行完了配置之后就不会再使用的了。

ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
//用系统类加载器,从类路径中加载资源

通过源码分析可以发现其实Resources.getResourceAsStream是调用了ClassLoadergetResouces()方法,只不过做了一层封装。

<mapper url="file://D:/student.xml"/>

这个属性标注上了可以从绝对路径中加载资源

3. 事务管理机制剖析

<transactionManager type="JDBC"/>

可以在核心配置文件中为JDBC配置了事务管理器,而type可以写JDBCMANAGED其中的一个,而JDBC底层的管理机制还是借助于JDBC的事务处理机制,比如

connection.setAutoCommit(false);//管理事务
....业务处理....
coneection.commit();

MANAGED事务管理器:Mybatis不再负责事务的管理了。事务管理交给其他容器来负责,例如Spring,对于我们当前的单纯只有Mybatis的情况下,如果配置为MANAGED那么事务这块就没有开启了。

使用JDBC事务管理器的话,底层创建的事务管理器对象:JdbcTrasaction对象,如果编写的代码是

SqlSession sqlSession = sqlSessionFactory.openSession(true);
//表示没有开启事务,因为这种方式不会执行conn.setAutoCommit(false);
//那么默认的是setAutoCommit(true),表示自动提交事务,这种情况就叫做没有事务管理

为了帮助记忆,我们可以在这里的标志符,false表示关闭自动提交,true表示开启自动提交

这主要是因为connection.getAutoCommit()原来默认就是true的,而你如果传进来一个true的话,那么

if(true != true){
    //这个条件一定不成立,那么就一定不会执行里面的代码,如果你传进来一个false,就会传进来这个代码
    connection.setAutoCommit(false);//对于jdbc而言,就是关闭提交了
}

4. Junit与logback的使用

一般来说基本的测试框架如下:

@Test
public void testAddStudent(){
    //单元测试中有两个重要的概念
    //一个是实际值
    //一个是期望值
    int actual = 2;
    int expected = 3;
    //加断言进行测试
    Assert.assertEquals(expected,actual);
}
  • 在Mybatis中引用Junit

只需要直接导入Junit即可:

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.10</version>
    </dependency>
</dependencies>
  • 在Mybatis中集成loback

首先了解一下常见的集成日志组件

  • SLF4J:是一个日志标准,其中有一个框架叫做logback,实现了相关的规范
  • LOG4J
  • LOG4J2
  • STDOUT_LOGGING标准日志输出,由Mybatis实现

配置实例

<settings>
    <!--当没有指定的时候将会自动查找,将会按照你集成的环境进行查找-->
    <setting name="logImpl" value="SLF4J"/>
    <setting name="logImpl" value="LOG4J"/>
    <setting name="logImpl" value="LOG4J2"/>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

这个标签应该要出现在environments这些规定是在dtd文件中规定的。

集成logback日志框架:

  • 导入logback的依赖
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
</dependency>
  • 配置logback的配置文件,名字是logback.xml或者logback-test.xml,不能是其他的名字,这个配置文件必须放到类的根路径下,不能是其他位置,它的优先级是先找logback-test.xml然后再找logback.xml
<?xml version="1.0" encoding="UTF-8"?>

<!-- 配置文件修改时重新加载,默认true -->
<configuration debug="false">

    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder" charset="UTF-8">
            <!-- 输出日志记录格式 -->
            <pattern>[%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>

5. 封装Mybatis工具类

public class SqlSessionUtil {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        //类加载时执行
        //类加载时解析mybatis-config.xml文件
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //为了防止new对象
    private SqlSessionUtil(){}

    /**
     * 获取sqlSession对象
     * @return
     */
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession(false);
    }
}

6. 使用Java对象对insert操作进行传参

6.1 使用Map集合

在JDBC中占位符采用的是?而在mybatis的则是?等效的写法是#{},必须使用#{}来代替?

public static void testMybatis(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    try{
        //需要一个对象进行数据的封装
        Map<String,Object> map = new HashMap<>();
        map.put("id","6");
        map.put("name","周八");
        map.put("tutor","周八师");
        sqlSession.insert("addStudent",map);
    }finally {
        sqlSession.close();
    }
}
<insert id = "addStudent">
    insert into student(id,name,tutor) values(#{id},#{name},#{tutor});
</insert>

Java程序中可以使用MapSQL语句的占位符传值,如果它找不到这个key,那么它就会取到一个null

其实这也印证了一点,它底层实际上是通过包装Map集合来对数据进行存储使用的。

6.2 使用pojo对象

//如果使用的是domain的对象
Student student = new Student("6","name","tutor");
sqlSession.insert("addStudent",student);

如果通过domain对象进行取值绑定数据的情况下,其底层是调用其getXXX()方法来获取相关的数据的。因此为了做到通过pojo绑定数据,因此必须为实体类提供getXXX()方法。而在#{}里面提供的属性名是xXX(注意大小写)

也就是说mybatis在底层给?传值的时候,先要获取值,调用了pojoget方法 。

6.3 CURD汇总

<?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="cn.edu.scau.cmi.zhangtingjie.orm.mybatis.StudentDAO">
    <insert id = "addStudent">
        insert into student(id,name,tutor) values(#{id},#{name},#{tutor});
    </insert>

    <update id="updateStudent">
        update student
        set name = #{name},tutor = #{tutor}
        where id = #{id};
    </update>

    <delete id="deleteStudent">
        delete from student where id = #{id};
    </delete>

    <select id="findStudent" resultType="cn.edu.scau.cmi.zhangtingjie.orm.domain.Student">
        select * from student where id = #{id};
    </select>

</mapper>
public interface StudentDAO {
    /**
     * 增加学生
     * @param student
     * @return
     */
    boolean addStudent(Student student);

    /**
     * 删除学生
     * @param id
     * @return
     */
    boolean deleteStudent(String id);

    /**
     * 查询学生
     * @param id
     * @return
     */
    Student findStudent(String id);

    /**
     * 更新学生信息
     * @param student
     * @return
     */
    boolean updateStudent(Student student);
}

6.4 查询一个

//查询一个
try (SqlSession sqlSession = SqlSessionUtil.openSession()) {
    sqlSession.selectOne("selectOne", 1);
}

执行DQL语句进行查询,根据ID进行查询,返回结果一定会是一条,Mybatis底层执行了select语句之后,一定会返回一个结果集对象ResultSet,它在JDBC中是ResultSet,接下来就是Mybatis应该从ResultSet中取出数据,封装为java对象 。

因此对于查询语句,一般来说需要封装对象类型,对于查询回来的字段,利用orm原理,将字段映射回java对象。

<select id="selectOne" resultType="domain.Student">
    select * from student where id = #{id};
</select>

因此在这里需要给mybatis透露一个信息,关于返回数据的类型,它会自动封装相关的数据到对象里面,然后使用。

一定要注意,当你的数据库中存在有下划线的时候,这时候会出现赋值失败的原因,可以分析一下

select * from t_car where id = 1;

当执行完毕之后,返回的结果集是

id car_num brand guide_price produce_time car_type

Car类的属性名

carNum,guidePrice,produceTime,carType

可以看到在这种情况下,这些列名和Car类中的属性名对不上,因此在这种方法下赋值不上去。

注意:在学完了boot回来之后,发现之前是没有这个问题的,这是因为boot中封装了相关的驼峰命名和下划线命名的映射相关API,因此自动完成了映射,不需要我们完成这些处理

那么在这里的话,为了让我们的结果集对上,这样的话就需要使用as关键字,利用as关键字重命名,让对象属性和数据库字段完全对上。

select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType

6.5 查询所有

   <select id="selectAll" resultType="domain.Student">
       select * from student;
   </select>
<!--注意:如果有下划线的话一定还是要取别名的,而我这个表是没有下划线命名的,因此没有做-->
public void testSelectAll(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    List<Student> selectAll = sqlSession.selectList("selectAll");
    for (Student student : selectAll) {
        System.out.println(student);
    }
}

注意resultType要指定元素的类型,这是因为selectList(),你需要一个列表,但是它无法确定你列表内部所需要的元素,所以的话你要给它提供这个信息。

7. namespace的作用

在一种情况下,在两个mapper.xml文件中存在两个完全相同的sqlId,这时候Mybatis就会蒙圈,不知道你指的这个SqlID到底具体是哪个SQL,这时候就会抛出无效映射的异常。

namespace的作用主要是用来防止SQL的ID冲突。

因此实际上在Mybatis中执行的SQL的时候,它有两个步骤,首先它要查找到这个sqlId,如果你的SqlId在全局是唯一的,那么它就能直接执行。如果你的SqlId不是全局唯一的,而是存在于多个命名空间之中,这时候就需要先通过命名空间找到这个SqlId,然后再通过SqlId找到对应的SQL语句进行执行。

8. Mybatis核心配置文件配置多环境

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--根标记是configuration,标志这个文件是配置文件,还有的有mapper,mapper文件则是mapper标记-->
<!--同时需要在xml文件提供它的dtd文件,它用来检查xml文件的合法性-->
<environments default="development">
    <!--默认环境:可以用来切换SqlSeesionFactory,如果你没有指定环境,那么就使用你default指定的值-->
    <!--配置多个环境,可以存在有多个environment,切换environment只需要修改选择的值-->
    <!--在mybatis中,一般一个环境对应一个数据库-->
    <!--一般来说,一个数据库会对应一个SqlSessionFactory对象-->
    <!--一个环境environment会对应一个SqlSessionFactory对象-->
    <!--这是因为一个SqlSessionFactory是由builder来的,而builder是通过xml文件读出来的-->
</environments>
public SqlSessionFactory build(InputStream inputStream, String environment) {
  return build(inputStream, environment, null);
}

同时也可以通过这种形式进行环境的指定,对environment进行指定,但是这种就相当于在代码中写了配置信息,这是不符合开闭原则的。

<transactionManager type="JDBC"/>

作用:配置事务管理器,指定mybatis具体使用什么方式去管理事务

public interface Transaction {
  Connection getConnection() throws SQLException;
    
  void commit() throws SQLException;

  void rollback() throws SQLException;

  void close() throws SQLException;

  Integer getTimeout() throws SQLException;
}

JDBC事务管理器底层源代码

public class JdbcTransaction implements Transaction {

  private static final Log log = LogFactory.getLog(JdbcTransaction.class);

  protected Connection connection;
  protected DataSource dataSource;
  protected TransactionIsolationLevel level;
  protected boolean autoCommit;
  protected boolean skipSetAutoCommitOnClose;

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    this(ds, desiredLevel, desiredAutoCommit, false);
  }

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit, boolean skipSetAutoCommitOnClose) {
    dataSource = ds;
    level = desiredLevel;
    autoCommit = desiredAutoCommit;
    this.skipSetAutoCommitOnClose = skipSetAutoCommitOnClose;
  }

  public JdbcTransaction(Connection connection) {
    this.connection = connection;
  }

  @Override
  public Connection getConnection() throws SQLException {
    if (connection == null) {
      openConnection();
    }
    return connection;
  }

  @Override
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

  @Override
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }

  @Override
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close();
    }
  }

  protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
    try {
      if (connection.getAutoCommit() != desiredAutoCommit) {
        if (log.isDebugEnabled()) {
          log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(desiredAutoCommit);
      }
    } catch (SQLException e) {
      // Only a very poorly implemented driver would fail here,
      // and there's not much we can do about that.
      throw new TransactionException("Error configuring AutoCommit.  "
          + "Your driver may not support getAutoCommit() or setAutoCommit(). "
          + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
    }
  }

  protected void resetAutoCommit() {
    try {
      if (!skipSetAutoCommitOnClose && !connection.getAutoCommit()) {
        // MyBatis does not call commit/rollback on a connection if just selects were performed.
        // Some databases start transactions with select statements
        // and they mandate a commit/rollback before closing the connection.
        // A workaround is setting the autocommit to true before closing the connection.
        // Sybase throws an exception here.
        if (log.isDebugEnabled()) {
          log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(true);
      }
    } catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Error resetting autocommit to true "
            + "before closing the connection.  Cause: " + e);
      }
    }
  }

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommit);
  }

  @Override
  public Integer getTimeout() throws SQLException {
    return null;
  }

}
public class ManagedTransaction implements Transaction {

  private static final Log log = LogFactory.getLog(ManagedTransaction.class);

  private DataSource dataSource;
  private TransactionIsolationLevel level;
  private Connection connection;
  private final boolean closeConnection;

  public ManagedTransaction(Connection connection, boolean closeConnection) {
    this.connection = connection;
    this.closeConnection = closeConnection;
  }

  public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
    this.dataSource = ds;
    this.level = level;
    this.closeConnection = closeConnection;
  }

  @Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }

  @Override
  public void commit() throws SQLException {
    // Does nothing
  }

  @Override
  public void rollback() throws SQLException {
    // Does nothing
  }

  @Override
  public void close() throws SQLException {
    if (this.closeConnection && this.connection != null) {
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + this.connection + "]");
      }
      this.connection.close();
    }
  }

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    this.connection = this.dataSource.getConnection();
    if (this.level != null) {
      this.connection.setTransactionIsolation(this.level.getLevel());
    }
  }

  @Override
  public Integer getTimeout() throws SQLException {
    return null;
  }

}

可以看到其底层是通过接口实现不同子类来实现的。

数据源配置:

<dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/orm?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>

数据源的作用是为程序提供conncetion对象,但凡是给程序提供连接对象的都是数据源,其实是一套规范

public interface DataSource  extends CommonDataSource, Wrapper {
  Connection getConnection() throws SQLException;
  Connection getConnection(String username, String password)
    throws SQLException;
}

如果我们自己编写了实现子类,就可以实现一个数据源组件。数据库连接池就是提供连接对象的,所以数据库连接池就是一个数据源。因此,只要实现了这一套规范,我们就认为它是一个数据源组件

常见的数据源组件(数据库连接池):如Durid、c3p0、dbcp

<dataSource type="POOLED">
<!--这里指明了数据库连接池的组件-->
  • POOLED:Mybatis自己实现的连接池
  • UNPOOLED:不使用数据库连接池技术,每次都创建新的连接池对象
  • JNDI:集成第三方连接池

大部分的JNDI都实现了JNDI规范,Java命名目录接口,JavaNamingAndDirectoryInterface

为什么要实现这个规范呢,这是为了便于各种第三方框架进行集成,所谓命名目录接口规范,这是为了各种框架在集成的时候,能够有一套统一的标准对这些组件进行路径寻址,以Tomcat集成Mybatis为例,Tomcat中实现了这个规范,那么Tomcat就能够在集成了C3P0、Durid的情况下,能够为Mybatis提供路径寻址,从而能够使得不同的组件集合起来工作,这就是规范的作用。

POOLEDUNPOOLED的区别

主要是在底层进行JDBC处理的不同

对于POOLED:它在使用完一个connection的时候,会有一个return操作,也就是说建立好了之后把连接对象返回到池中,方便下次调用,同时连接池也类似于线程池,它也可以控制连接的数量,如果超过了这个数量,就会导致线程阻塞等待获取连接。如果等待超时,就会把现有的连接对象拿回到连接池中,给别人用。

对于UNPOOLED:每次的JDBC操作都新建一个conncetion

  • 具体参数的配置
<!--设置好参数可以让连接池发挥更好的效果-->
<!--简单来说就是进行调参-->
<!--连接池当中最多的正在使用的连接对象的数量上限:最多有多少个连接在活动状态,默认是10-->
<property name="poolMaximumActiveConnections" value="10"/>
<!--在强制返回之前,池中连接过期的时间,也就是当一个线程等待了这个时间之后,会强制剥夺一个连接到连接池中-->
<property name="poolMaximumCheckoutTime" value="20000"/>
<!--如果获取连接花费了相当长的时间,连接池会打印日志状态并且重新尝试获取一个日志-->
<property name="poolTimeToWait" value="20000"/>
<!--配置最多的空闲数量,当多于这个数量的时候,就关闭一个-->
<property name="poolMaximumIdleConnections" value="5"/>
<!--使得mybatis的配置更加灵活。-->
<properties>
    <property name="key" value="value"/>
</properties>

文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录