mybatis

来源:互联网 发布:linux传送文件命令 编辑:程序博客网 时间:2024/05/22 14:08

目录:

一   ...mybatis原理

二   ...mybatis延迟加载

三   ...mybatis一级缓存 二级缓存

四   ...mybatis实现多数据库兼容

五   ...mybatis #和$的区别

六   ...mybatis关联查询

一、mybatis原理

1.JDBC查询数据库

⑴加载驱动

⑵获取数据库连接

⑶创建JDBC Statements对象

⑷设置传入参数

⑸执行并获得查询结果

⑹对查询结果进行转换处理并将处理结果返回

⑺释放资源

public static List<Map<String,Object>> queryForList(){      Connection connection = null;      ResultSet rs = null;      PreparedStatement stmt = null;      List<Map<String,Object>> resultList = new ArrayList<Map<String,Object>>();      try {          // 加载JDBC驱动          Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();          String url = "jdbc:oracle:thin:@localhost:1521:ORACLEDB";          String user = "trainer";           String password = "trainer";           // 获取数据库连接          connection = DriverManager.getConnection(url,user,password);           String sql = "select * from userinfo where user_id = ? ";          // 创建Statement对象(每一个Statement为一次数据库执行请求)          stmt = connection.prepareStatement(sql);          // 设置传入参数          stmt.setString(1, "zhangsan");          // 执行SQL语句          rs = stmt.executeQuery();          // 处理查询结果(将查询结果转换成List<Map>格式)          ResultSetMetaData rsmd = rs.getMetaData();          int num = rsmd.getColumnCount();          while(rs.next()){              Map map = new HashMap();              for(int i = 0;i < num;i++){                  String columnName = rsmd.getColumnName(i+1);                  map.put(columnName,rs.getString(columnName));              }              resultList.add(map);          }      } catch (Exception e) {          e.printStackTrace();      } finally {          try {              // 关闭结果集              if (rs != null) {                  rs.close();                  rs = null;              }              // 关闭执行              if (stmt != null) {                  stmt.close();                  stmt = null;              }              if (connection != null) {                  connection.close();                  connection = null;              }          } catch (SQLException e) {              e.printStackTrace();          }      }            return resultList;  }
2.JDBC转换到Mybatis过程

⑴第一步优化:获取连接和释放

使用数据库连接池来解决数据库频繁开启和关闭引发的性能问题。我们统一从DataSource(内部封装了一个连接池)获取数据库连接,DataSource具体由DBCP实现还是由容器JNDI实现都可以。

⑵第二步优化:sql统一存取

不把SQL放到java代码中,将sql统一放到配置文件或者数据库(以K-V形式存放)里面

⑶第三步优化:传入参数映射和动态sql

使用标签来处理传入参数数量不确定的问题

⑷第四步优化:结果映射和结果缓存

JDBC需要将ResultSet对象数据取出来。mybatis调用封装的方法告诉SQL返回对象的数据类型。

⑸第五步优化:重复sql处理

将重复的代码独立成一个sql块

3.mybatis框架整体设计


3.1接口层和数据库的交互方式

⑴使用mybatis提供的API(不符合趋势)

创建一个sqlsession对象,然后根据statement Id和参数操作数据库

⑵使用Mapper接口

mybatis将配置节点每一个<mapper>节点抽象为一个mapper接口,节点的id值为Mapper接口的方法名称。

3.2数据处理层(mybatis核心)

数据处理层主要是构造执行sql语句以及封装结果集

3.3框架支撑层

⑴事务管理机制

⑵连接池管理机制

⑶缓存机制

⑷sql语句的配置方式

3.4引导层

配置和引导mybatis配置信息的方式。mybatis提供两种方式配置mybatis,基于XML配置文件的方式和基于java API的方式。

3.5主要构件

SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;

Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;

StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。

ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数;

ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;

TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换;

MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装;

SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回;

BoundSql:表示动态生成的SQL语句以及相应的参数信息;

Configuration:MyBatis所有的配置信息都维持在Configuration对象之中;

4.mybatis初始化机制

Mybatis初始化过程可以用一句话概括:就是将Mybatis的配置信息加载到一个类中、供后面Mybatis进行各种操作时使用。

主要分为三个步骤:

⑴加载配置文件

⑵解析配置文件,将配置文件的信息装载到Configuration中

⑶根据Configuration创建SqlSessionFactory并返回



二、mybatis 延迟加载

1.什么是延迟加载

又叫做懒加载

resultMap可以实现高级映射(使用associationcollection实现一对一及一对多映射),associationcollection具备延迟加载功能。

延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

2.使用association实现延迟加载

⑴、需求:查询订单并且关联查询用户信息

⑵、mapper.xml,需要定义两个mapper的方法对应的statement

 只查询订单信息

SELECT * FROM orders

在查询订单的statement中使用association去延迟加载(执行)下边的statement。

<!-- 查询订单关联查询用户,用户信息需要延迟加载 -->        <select id="findOrdersUserLazyLoading" resultMap="OrderUserLazyLoadingResultMap">            SELECT * FROM orders        </select>    
通过上边查询到的订单信息中user_id去关联查询用户信息

使用UserMapper.xml中的findUserById

<select id="findUserById" resultType="user" parameterType="int">            SELECT * FROM USER WHERE id=#{value}        </select>   
  执行思路:

上边先去执行findOrdersuserLazyLoading,当需要的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来。

⑶、延迟加载的resultMap

<resultMap type="cn.itcast.mybatis.po.Orders" id="OrderUserLazyLoadingResultMap">        <!-- 对订单信息进行映射配置 -->        <id column="id" property="id"/>        <result column="user_id" property="userId"/>        <result column="number" property="number"/>        <result column="createtime" property="createtime"/>        <result column="note" property="note"/>        <!-- 对用户信息进行延迟加载 -->        <!--         select:指定延迟加载要执行的statement的id(是根据user_id查询用户信息是statement)        要使用UserMapper.xml中findUserById完成根据用户id(user_id)对用户信息的查询,如果findUserById不在本mapper中,        需要前边加namespace        column:订单信息中关联用户信息的列,是user_id         -->        <association property="user" javaType="cn.itcast.mybatis.po.User"         select="cn.itcast.mybatis.mapper.UserMapper.findUserById" column="user_id">                </association>        </resultMap>   
使用association中是select指定延迟加载去执行的statementid
⑷、打开延迟加载开关
<settings>            <!-- 打开延迟加载的开关 -->            <setting name="lazyLoadingEnabled" value="true" />            <!-- 将积极加载改为消息加载即按需加载 -->            <setting name="aggressiveLazyLoading" value="false"/>        </settings>    

lazyLoadingEnabled:设置懒加载,默认为false。如果为false:则所有相关联的都会被初始化加载。

aggressiveLazyLoading:默认为true。当设置为true时,懒加载的对象可能被任何懒属性全部加载;否则,每个属性按需加载。

3、使用collection实现延迟加载

同理。

4、总结

⑴不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载?
实现思路:

定义两个mapper方法:

① 查询订单列表  ②根据用户id查询用户信息

先去查询第一个mapper方法,获取订单信息列表;在程序中(service),按需去调用第二个mapper方法去查询用户信息。

⑵使用延迟加载方法,先去查询简单的sql(最好单表,也可关联查询),再去按需加载关联查询的其他信息。



三、mybatis一级缓存 二级缓存

1.概念图


2.一级缓存

一级缓存是sqlSession级别缓存,不同sqlSession互不影响。在同一个sqlSession执行相同sql语句,第一次执行将取得到数据保存到缓存第二次获取数据就不再从数据库中查询了。随着sqlSession结束而消亡,是mybatis默认开启的缓存。

sqlSession去执行commit(插入,更新,删除)操作,就会清空sqlSession的一级缓存。

更多详情:

http://blog.csdn.net/luanlouis/article/details/41280959

3.二级缓存

二级缓存是同一个Mapper级别(可以是同一namespace的不同Mapper)的缓存,作用域是同一个namespace,不同sqlSession两次执行相同namespace的sql语句且传递参数相同,第一次会写入缓存第二次就从缓存取数据,需要在setting配置全局参数中配置开启二级缓存。

sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。

开启缓存:

在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关),cacheEnabled设置为 true

在映射文件中,加入以下内容,开启二级缓存:


需要给缓存的对象执行序列化。如果该类存在父类,那么父类也要实现序列化。


禁用二级缓存

该statement中设置userCache=false可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。


刷新二级缓存

在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。

设置statement配置中的flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

如下:

<insertid="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">

 总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存默认情况下为true,我们不用去设置它,这样可以避免数据库脏读。


四、mybatis实现多数据库兼容
mybatis3.1.1起,本身可以支持多数据库。

首先你要在mybatis.xml文件中添加如下配置:

<databaseIdProvider type="DB_VENDOR">      <property name="SQL value="sqlserver"/>      <property name="DB2" value="db2"/>              <property name="Oracle" value="oracle" />        <property name="Adaptive Server Enterprise" value="sybase"/>         <property name="MySQL" value="mysql" />    </databaseIdProvider><!-- name是数据库厂商名,value是你自己的标识名 -->
这个name如果不知道该填什么,可以用如下代码获取:
[java] view plain copy
  1. Connection conn = dataSource.getConnection();      
  2. DatabaseMetaData metaData = conn.getMetaData();      
  3. return metaData.getDatabaseProductName();  
然后,在sql映射文件里。要如下写sql,在后面加上数据库标记
[java] view plain copy
  1. <?xml version="1.0" encoding="UTF-8" ?>     
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">    
  3. <mapper namespace="com.boco.iwms.base.dao.BasicSqlDao">   
  4.     <select id="getCountOfSql" resultType="int" useCache="false" statementType="STATEMENT" timeout="5000" databaseId="mysql">   
  5.         <![CDATA[  
  6.             SELECT COUNT(*) FROM user  
  7.         ]]>   
  8.     </select>   
  9.          
  10.     <select id="getCountOfSql" resultType="int" useCache="false" statementType="STATEMENT" timeout="5000" databaseId="oracle">   
  11.         <![CDATA[  
  12.             SELECT COUNT(*) FROM user  
  13.         ]]>   
  14.     </select>       
  15. </mapper>  
这样程序会自动识别数据库,根据你配置的value和databaseId来寻找合适的sql方言。

五、mybatis #和$的区别

1 #是将传入的值当做字符串的形式,eg:select id,name,age from student where id =#{id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id ='1'.

2 $是将传入的数据直接显示生成sql语句,eg:select id,name,age from student where id =${id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id = 1.

3 使用#可以很大程度上防止sql注入。(语句的拼接)

4 但是如果使用在order by 中就需要使用 $.

5 在大多数情况下还是经常使用#,但在不同情况下必须使用$.


六、mybaits关联查询

1.一对一关联

⑴需求描述

假设一个班只有一个老师,我们查班级信息的时候查询老师信息

⑵建表

CREATE TABLE teacher(    t_id INT PRIMARY KEY AUTO_INCREMENT,     t_name VARCHAR(20));CREATE TABLE class(    c_id INT PRIMARY KEY AUTO_INCREMENT,     c_name VARCHAR(20),     teacher_id INT);ALTER TABLE class ADD CONSTRAINT fk_teacher_id FOREIGN KEY (teacher_id) REFERENCES teacher(t_id);    INSERT INTO teacher(t_name) VALUES('teacher1');INSERT INTO teacher(t_name) VALUES('teacher2');INSERT INTO class(c_name, teacher_id) VALUES('class_a', 1);INSERT INTO class(c_name, teacher_id) VALUES('class_b', 2);

⑶创建实体类

班级类class:

package me.gacl.domain;/** * @author gacl * 定义class表对应的实体类 */public class Classes {    //定义实体类的属性,与class表中的字段对应    private int id;            //id===>c_id    private String name;    //name===>c_name        /**     * class表中有一个teacher_id字段,所以在Classes类中定义一个teacher属性,     * 用于维护teacher和class之间的一对一关系,通过这个teacher属性就可以知道这个班级是由哪个老师负责的     */    private Teacher teacher;    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Teacher getTeacher() {        return teacher;    }    public void setTeacher(Teacher teacher) {        this.teacher = teacher;    }    @Override    public String toString() {        return "Classes [id=" + id + ", name=" + name + ", teacher=" + teacher+ "]";    }}
教师类Teacher:

package me.gacl.domain;/** * @author gacl * 定义teacher表对应的实体类 */public class Teacher {    //定义实体类的属性,与teacher表中的字段对应    private int id;            //id===>t_id    private String name;    //name===>t_name    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return "Teacher [id=" + id + ", name=" + name + "]";    }}
⑷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,namespace的值习惯上设置成包名+sql映射文件名,这样就能够保证namespace的值是唯一的例如namespace="me.gacl.mapping.classMapper"就是me.gacl.mapping(包名)+classMapper(classMapper.xml文件去除后缀) --><mapper namespace="me.gacl.mapping.classMapper">    <!--         根据班级id查询班级信息(带老师的信息)        ##1. 联表查询        SELECT * FROM class c,teacher t WHERE c.teacher_id=t.t_id AND c.c_id=1;                ##2. 执行两次查询        SELECT * FROM class WHERE c_id=1;  //teacher_id=1        SELECT * FROM teacher WHERE t_id=1;//使用上面得到的teacher_id     -->    <!--     方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集             封装联表查询的数据(去除重复的数据)        select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=1    -->    <select id="getClass" parameterType="int" resultMap="ClassResultMap">        select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id}    </select>    <!-- 使用resultMap映射实体类和字段之间的一一对应关系 -->    <resultMap type="me.gacl.domain.Classes" id="ClassResultMap">        <id property="id" column="c_id"/>        <result property="name" column="c_name"/>        <association property="teacher" javaType="me.gacl.domain.Teacher">            <id property="id" column="t_id"/>            <result property="name" column="t_name"/>        </association>    </resultMap>        <!--     方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型        SELECT * FROM class WHERE c_id=1;        SELECT * FROM teacher WHERE t_id=1   //1 是上一个查询得到的teacher_id的值    -->     <select id="getClass2" parameterType="int" resultMap="ClassResultMap2">        select * from class where c_id=#{id}     </select>     <!-- 使用resultMap映射实体类和字段之间的一一对应关系 -->     <resultMap type="me.gacl.domain.Classes" id="ClassResultMap2">        <id property="id" column="c_id"/>        <result property="name" column="c_name"/>        <association property="teacher" column="teacher_id" select="getTeacher"/>     </resultMap>          <select id="getTeacher" parameterType="int" resultType="me.gacl.domain.Teacher">        SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}     </select></mapper>

2.一对多关联查询

⑴需求描述:

根据classId查询对应的班级信息,包括学生(一对多),老师(依然是一对一)

⑵建表:

班级和教师表不变,我们这里再创建一个学生表:

CREATE TABLE student(    s_id INT PRIMARY KEY AUTO_INCREMENT,     s_name VARCHAR(20),     class_id INT);INSERT INTO student(s_name, class_id) VALUES('student_A', 1);INSERT INTO student(s_name, class_id) VALUES('student_B', 1);INSERT INTO student(s_name, class_id) VALUES('student_C', 1);INSERT INTO student(s_name, class_id) VALUES('student_D', 2);INSERT INTO student(s_name, class_id) VALUES('student_E', 2);INSERT INTO student(s_name, class_id) VALUES('student_F', 2);
⑶建类:

这里教师类不变,我们修改班级类,并添加学生类:

package me.gacl.domain;import java.util.List;/** * @author gacl * 定义class表对应的实体类 */public class Classes {    //定义实体类的属性,与class表中的字段对应    private int id;            //id===>c_id    private String name;    //name===>c_name        /**     * class表中有一个teacher_id字段,所以在Classes类中定义一个teacher属性,     * 用于维护teacher和class之间的一对一关系,通过这个teacher属性就可以知道这个班级是由哪个老师负责的     */    private Teacher teacher;    //使用一个List<Student>集合属性表示班级拥有的学生    private List<Student> students;    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Teacher getTeacher() {        return teacher;    }    public void setTeacher(Teacher teacher) {        this.teacher = teacher;    }    public List<Student> getStudents() {        return students;    }    public void setStudents(List<Student> students) {        this.students = students;    }    @Override    public String toString() {        return "Classes [id=" + id + ", name=" + name + ", teacher=" + teacher                + ", students=" + students + "]";    }}
添加学生类:

package me.gacl.domain;/** * @author gacl * 定义student表所对应的实体类 */public class Student {    //定义属性,和student表中的字段对应    private int id;            //id===>s_id    private String name;    //name===>s_name        public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return "Student [id=" + id + ", name=" + name + "]";    }}
⑷XML文件:

<!--         根据classId查询对应的班级信息,包括学生,老师     -->    <!--     方式一: 嵌套结果: 使用嵌套结果映射来处理重复的联合结果的子集    SELECT * FROM class c, teacher t,student s WHERE c.teacher_id=t.t_id AND c.C_id=s.class_id AND  c.c_id=1     -->    <select id="getClass3" parameterType="int" resultMap="ClassResultMap3">        select * from class c, teacher t,student s where c.teacher_id=t.t_id and c.C_id=s.class_id and  c.c_id=#{id}    </select>    <resultMap type="me.gacl.domain.Classes" id="ClassResultMap3">        <id property="id" column="c_id"/>        <result property="name" column="c_name"/>        <association property="teacher" column="teacher_id" javaType="me.gacl.domain.Teacher">            <id property="id" column="t_id"/>            <result property="name" column="t_name"/>        </association>        <!-- ofType指定students集合中的对象类型 -->        <collection property="students" ofType="me.gacl.domain.Student">            <id property="id" column="s_id"/>            <result property="name" column="s_name"/>        </collection>    </resultMap>        <!--         方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型            SELECT * FROM class WHERE c_id=1;            SELECT * FROM teacher WHERE t_id=1   //1 是上一个查询得到的teacher_id的值            SELECT * FROM student WHERE class_id=1  //1是第一个查询得到的c_id字段的值     -->     <select id="getClass4" parameterType="int" resultMap="ClassResultMap4">        select * from class where c_id=#{id}     </select>     <resultMap type="me.gacl.domain.Classes" id="ClassResultMap4">        <id property="id" column="c_id"/>        <result property="name" column="c_name"/>        <association property="teacher" column="teacher_id" javaType="me.gacl.domain.Teacher" select="getTeacher2"></association>        <collection property="students" ofType="me.gacl.domain.Student" column="c_id" select="getStudent"></collection>     </resultMap>          <select id="getTeacher2" parameterType="int" resultType="me.gacl.domain.Teacher">        SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}     </select>          <select id="getStudent" parameterType="int" resultType="me.gacl.domain.Student">        SELECT s_id id, s_name name FROM student WHERE class_id=#{id}     </select>












原创粉丝点击