Loading

01、Mybatis

一、引言

1.1、什么是框架?

  软件的半成品,解决了软件开发过程当中的普适性问题,从而简化了开发步骤,提供了开发的效率。

1.2、什么是ORM框架?

  ORM (Object Relational Mapping)对象关系映射,将程序中的一个对象与表中的一行数据 一 一对应

  ORM框架提供了持久化类与表的映射关系,在运行时参照映射文件的信息,把对象持久化到数据库中

1.3、使用JDBC完成ORM操作的缺点?

  存在大量的冗余代码。

  手工创建Connection. Statement等。

  手工将结果集封装成实体对象。

  查询效率低,没有对数据访问进行过优化(Not Cache)。

二、MyBatis框架

2.1、概念

  MyBatis本是Apache软件基金会的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了Google Code,并且改名为MyBatis。2013年11月迁移到Github。

  MyBatis是一个优秀的基于Java的持久层框架,支持自定义SQL,存储过程和高级映射。

  MyBatis对原有JDBC操作进行了封装,几乎消除了所有JDBC代码,使开发者只需关注SQL本身。

  MyBatis可以使用简单的XML或Annotation来配置执行SQL,并自动完成ORM操作,将执行结果返回。

2.2、访问与下载

官方网站: http://www.mybatis.org/mybatis-3/

下载地址: https://github.com/mybatis/mybatis-3/releases/tag/mybatis-3.5.1

三、构建Maven项目

3.1、新建项目

3.2、选择Maven目录

3.3、GAV坐标

四、MyBatis环境搭建【重点】

4.1、pom.xml中引入MyBatis核心依赖

  在pom.xml中引入相关依赖

<?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.huiruan</groupId>
    <artifactId>hello_mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--依赖-->
    <dependencies>
        <!--mybatis核心依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <!--MySQL驱动依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    </dependencies>

</project>
View Code

4.2、创建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" >
<!--mybatis配置-->
<configuration>
    <!--jdbc环境配置、选中默认环境-->
    <environments default="MySqlDB">
        <!--mysql数据库环境配置-->
        <environment id="MySqlDB">
            <!--事务管理-->
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--&转义&-->
                <property name="url" value="jdbc:mysql://localhost:3306/XXX?useUnicode=true&characterEncoding=utf-8"/>
                <property name="username" value="XXX"/>
                <property name="password" value="XXX"/>
            </dataSource>
        </environment>
    </environments>

    <!--Mapper注册-->
    <mappers>
        <!--注册Mapper文件的所在位置-->
        <mapper resource="XXXMapper.xml"/>
    </mappers>
</configuration>
View Code

注意:

  mapper.xml默认建议存放在resources中,路径不能以/开头

五、MyBatis开发步骤【重点】

5.1、建表

CREATE TABLE t_users(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (50),
PASSWORD VARCHAR (50),
sex VARCHAR(1),
birthday DATETIME,
registTime DATETIME
)DEFAULT CHARSET = utf8;
View Code

5.2、定义实体类

  定义所需CURD操作的实体类

package com.huiruan;

import java.util.Date;

public class User {
    private Integer id;
    private String name;
    private String password;
    private String sex;
    private Date birthday;
    private Date registTime;

    public User() {
    }

    public User(Integer id, String name, String password, String sex, Date birthday, Date registTime) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.sex = sex;
        this.birthday = birthday;
        this.registTime = registTime;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Date getRegistTime() {
        return registTime;
    }

    public void setRegistTime(Date registTime) {
        this.registTime = registTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                ", registTime=" + registTime +
                '}';
    }
}
View Code

5.3、定义DAO接口

  根据所需DA0定义接口、以及方法

public interface UserMapper {
    public User selectUserById(Integer id);
}
View Code

5.4、编写Mapper.xml

  在resources目录中创建Mapper.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="com.huiruan.mapper.UserMapper">
    <!-- id =所需重写的接口抽象方法, resultType =查询后所需返回的对象类型-->
    <select id="selectUserById" resultType="com.huiruan.entity.User">
        <!-- #{arg0} =方法的第一个形参 -->
        select * from t_user where id = #{arg0}
    </select>
</mapper>
View Code

5.5、注册Mapper

  将Mapper.xml注册到mybatis-config.xml中

    <!--Mapper注册-->
    <mappers>
        <!--注册Mapper文件的所在位置-->
        <mapper resource="UserMapper.xml"/>
    </mappers>
View Code

5.6、测试一

  MyBatis的API操作方式

public class TestMybatis {
    @Test
    public void test1() throws IOException {
        //1.获得读取NyBatis配置文件的流对象
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        //2.构建SqlSession连接对象的工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        //3.通过工厂获得连接对象
        SqlSession sqlSession = factory.openSession();
        //通过连接对象获得接口实现类对象
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);
        //5.调用接口中的方法
        System.out.println(userDao.selectUserById(1));
    }

}
View Code

5.7、测试二【了解】

  iBatis传统操作方式

    @Test
    public void test2() throws IOException {
        // 1.获得读取NyBatis配置文件的流对象
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        // 2.构建SqlSession连接对象的工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        // 3.通过工厂获得连接对象
        SqlSession sqlSession = factory.openSession();
       // 4.通过连接对象直接调用接口中的方法
        Object o = sqlSession.selectOne("com.huiruan.mapper.UserMapper.selectUserById",1);
        System.out.println(o);
    }
View Code

六、细节补充

6.1、解决mapper.xml存放在resources以外路径中的读取问题

   在pom.xml文件最后追加< build >标签,以便可以将xml文件复制到classes中,并在程序运行时正确读取。

    <properties>
        <!-- 设置默认编码 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>*.xml</include>
                    <!-- 新添加*/代表1级目录**/代表多级目录 -->
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
View Code

  修改mybatis-config.xml

    <!--Mapper注册-->
    <mappers>
        <!--注册Mapper文件的所在位置-->
        <mapper resource="com/huiruan/mapper/UserMapper.xml"/>
    </mappers>
View Code

6.2、properties配置文件

  对于mybatis-config.xml的核心配置中,如果存在需要频繁改动的数据内容,可以提取到properties中。

#jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/huiruan?useUnicode=true&amp;characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
View Code

  修改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配置文件路径(外部配置、动态替换) -->
    <properties resource="jdbc.properties"></properties>

    <environments default="MySqlDB">
        <environment id="MySqlDB">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory">
                <!-- 使用 $ + 占位符-->
                <property name="driver" value="${jdbc.driver}"/>
                <!--&转义&amp;-->
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="com/huiruan/mapper/UserMapper.xml"/>
    </mappers>
</configuration>
View Code

6.3、类型别名

  为实体类定义别名,提高书写效率。

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

    <!-- 定义别名二选一 -->
    <typeAliases>
        <!-- 义类的别名 -->
        <typeAlias type="com.huiruan.entity.User" alias="user"/>

        <!-- 自动扫描包,将原类名作为别名 -->
        <!--<package name="com.huiruan.entity"/>-->
    </typeAliases>

    <environments default="MySqlDB">
        <environment id="MySqlDB">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory">
                <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="com/huiruan/mapper/UserMapper.xml"/>
    </mappers>
</configuration>
View Code

6.4、创建log4j配置文件

  pom.xml添加log4j依赖

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

  创建并配置log4j.properties

# Global logging configuration
log4j.rootLogger=DEBUG,stdout
# Console output. ..
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
# MyBatis logging configuration. . .
log4j.logger.org.mybatis.example.BlogMapper=TRACE
级别 描述
ALLLEVEL 打开所有日志记录开关;是最低等级的,用于打开所有日志记录。
DEBUG 输出调试信息;指出细粒度信息事件对调试应用程序是非常有帮助的。
INFO 输出提示信息;消息在粗粒度级别上突出强调应用程序的运行过程。
WARN 输出警告信息;表明会出现潜在错误的情形。
ERROR 输出错误信息;指出虽然发生错误事件,但仍然不影响系统的继续运行。
FATAL 输出致命错误;指出每个严重的错误事件将会导致应用程序的退出。
OFFLEVEL 关闭所有日志记录开关;是最高等级的,用于关闭所有日志记录。

 

七、MyBatis的CRUD操作【重点】

7.1、查询

  标签: <select id="" resultType="" >

7.1.1、序号参数绑定

    //    使用原生参数绑定
    public User selectUserByIdAndPwd(Integer id,String pwd);

 

    <select id="selectUserByIdAndPwd" resultType="user">
        select * from t_users where id = #{arg0} and password = #{arg1} <!-- arg0 arg1 arg2 -->
    </select>

    <select id="selectUserByIdAndPwd" resultType="user">
        select * from t_users where id = #{param1} and password = #{param2} <!-- param1 param2 param3 -->
    </select>

7.1.2、注解参数绑定【推荐】

    // 使用MyBatis提供的@Param进行参数绑定
    public User selectUserByIdAndPwd(@Param("id") Integer id,@Param("pwd") String pwd);

 

    <select id="selectUserByIdAndPwd" resultType="user">
        select * from t_users where id = #{id} and password = #{pwd}
    </select>

7.1.3、Map参数绑定

    // 添加Map进行参数绑定
    public User selectUserByIdAndPwd(Map value);

 

        HashMap hashMap = new HashMap(); //测试类创建Map
        hashMap.put("myID",1); //自定义key,绑定参数
        hashMap.put("myPwd","123456");
        System.out.println(userDao.selectUserByIdAndPwd(hashMap));

 

    <select id="selectUserByIdAndPwd" resultType="user">
        select * from t_users where id = #{myId} and password = #{myPwd} <!-- 通过key获得value -->
    </select>

7.1.4、对象参数绑定

    //使用对象属性进行参数绑定
    public User selectUserByUserInfo(User user);

 

    <select id="selectUserByUserInfo" resultType="user">
        select * from t_users where id = #{id} and password = #{password} <!--#{id}取User对象的id属性值、#ipassword}同理-->
    </select>

7.1.5、模糊查询

    public List<User> selectUsersByKeyword(@Param("keyword") String keyword);
    <select id="selectUsersByKeyword" resultType="user">
        select * from t_users where name like concat('%',#{keyword},'%') <!-- 拼接 -->
    </select>

7.2、删除

  标签: <delete id="" parameterType="" >

    <delete id="deleteUser" parameterType="int">
        delete from t_users
        where id = #{id}
    </delete>
public int deleteUser(@Param("id") Integer id);

7.4、添加

标签:  <insert id="" parameterType="">

    <!-- 手动主键 -->
    <insert id="insertUser" parameterType="User">
        insert into t_users(id,name,password,sex,birthday,registTime) values(#{id},#{name},#{password},#{sex},#{birthday},null);
    </insert>

    <!-- 自动主键 -->
    <insert id="insertUser" parameterType="User">
        <!-- 自动增长主键,以下两种方案均可 -->
        insert into t_users(id,name,password,sex,birthday,registTime) values(#{id},#{name},#{password},#{sex},#{birthday},null);
        <!--insert into t_users(id,name,password,sex,birthday,registTime) values(null,#{name},#{password},#{sex},#{birthday},null);-->
    </insert>

7.5、主键回填

标签: <selectKey id="" parameterType="" order="AFTER|BEFORE">

 7.5.1、通过last_insert_id()查询主键

    <insert id="insertProduct" parameterType="user">
        <selectKey keyProperty="id" resultType="int" order="AFTER"> <!-- 插入之后 -->
            select last_insert_id() <!-- 适用于整数类型自增主键 -->
        </selectKey>
        insert into t_users(id,name,password,sex,birthday,registTime) values(#{id},#{name},#{password},#{sex},#{birthday},null);
    </insert>

7.5.2、通过uuid()查询主键

CREATE TABLE t_order(
id VARCHAR(32) PRIMARY KEY,#字符型主键
NAME VARCHAR( 50)
) DEFAULT CHARSET = utf8;
    <insert id="insertOrder" parameterType="com.huiruan.entity.Order">
        <selectKey keyProperty="id" resultType="string" order="BEFORE"> <!-- 插入之前 -->
            select replace(uuid(),'-','') <!-- 适用于字符类型主键 -->
        </selectKey>
        insert into t_order(id,name)value(#{id},#{name})
    </insert>

八、MyBatis工具类【重点】

8.1、封装工具类

Resource:用于获得读取配置文件的I0对象,耗费资源,建议通过Io一次性读取所有所需要的数据

SqlSessionFactory: SqlSession工厂类,内存占用多,耗费资源,建议每个应用只创建一个对象

SqlSession:相当于Connection,可控制事务,应为线程私有,不被多线程共享

将获得连接、关闭连接、提交事务、回滚事务、获得接口实现类等方法进行封装

public class MyBatisUtils {

    // 获得sqlSession工厂
    private static SqlSessionFactory factory;

    // 创建ThreadLocal绑定当前线程中的SqlSession对象
    private static final ThreadLocal<SqlSession> tl = new ThreadLocal<SqlSession>();

    static {
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            factory = new SqlSessionFactoryBuilder().build(is);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 获得连接(从t1中获得当前线程Sq1Session)
    private static SqlSession openSession() {
        SqlSession session = tl.get();
        if (session == null) {
            session = factory.openSession();
            tl.set(session);
        }
        return session;
    }

    // 释放连接(释放当前线程中的SqlSession)
    private static void closeSession() {
        SqlSession session = tl.get();
        session.close();
        tl.remove();
    }

    // 提交事务(提交当前线程中的SqlSession所管理的事务)
    public static void commit() {
        SqlSession session = openSession();
        session.commit();
        closeSession();
    }

    // 回滚事务(回滚当前线程中的SqlSession所管理的事务)
    public static void rollback() {
        SqlSession session = openSession();
        session.rollback();
        closeSession();
    }

    // 获得接口实现类对象
    public static <T extends Object> T getMapper(Class<T> clazz) {
        SqlSession session = openSession();
        return session.getMapper(clazz);
    }

    public static SqlSession getSession(){
        return factory.openSession();
    }
}

8.2、测试工具类

  调用MyBatisUtils中的封装方法。 

    @Test
    public void testUtils(){
        try {
            UserMapper mapper = MyBatisUtils.getMapper(UserMapper.class);
            int i = mapper.deleteUser(4);
            System.out.println(i);
            MyBatisUtils.commit();
        } catch (Exception e) {
            e.printStackTrace();
            MyBatisUtils.rollback();
        }
    }
View Code

九、ORM映射【重点】

9.1、MyBatis自动ORM失效

  MyBatis只能自动维护库表"列名“与”属性名“相同时的――对应关系,二者不同时,无法自动ORM。

 

9.2、方案一: 列的别名

  在SQL中使用 as 为查询字段添加列别名,以匹配属性名。

    <select id="selectManagerByIdAndPwd" resultType="com.huiruan.entity.Manager">
        SELECT `mgr_id` as id,`mgr_name` as name,`mgr_pwd` as password
        FROM `t_managers` WHERE `mgr_id`=#{id} AND `mgr_pwd` = #{pwd}
    </select>

9.3、方案二:结果映射(ResultMap-查询结果的封装规则)

通过 <resultMap id="" type="">映射, 匹配列名与属性名。

    <!-- 定义resultMap标签 -->
    <resultMap id="managerResultMap" type="com.huiruan.entity.Manager">
        <!-- 关联列名与主键 -->
        <id column="mgr_id" property="id"></id>

        <!-- 关联列名与属性 -->
        <result column="mgr_name" property="name"></result>
        <result column="mgr_pwd" property="password"></result>
    </resultMap>

    <!-- 使用resultMap作为ORM映射依据 -->
    <select id="selectAllManager" resultMap="managerResultMap">
        SELECT `mgr_id`,`mgr_name`,`mgr_pwd` FROM `t_managers`
    </select>

十、MyBatis处理关联关系-多表连接【重点】

实体间的关系:关联关系(拥有has、属于belong)

OneToOne: 一对一关系(Passenger --- Passport)

OneToMany: 一对多关系(Employee --- Department)

ManyToMany: 多对多关系(Student --- Subject)

CREATE TABLE t_passengers (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (50),
sex VARCHAR (1),
birthday DATE
)DEFAULT CHARSET=utf8;


CREATE TABLE t_passports (
id INT PRIMARY KEY AUTO_INCREMENT,
nationality VARCHAR (50),
expire DATE,
passenger_id INT UNIQUE,
FOREIGN KEY (passenger_id) REFERENCES t_passengers(id)
)DEFAULT CHARSET=utf8; 

INSERT INTO t_passengers VALUES(NULL, 'shine_01','f','2018-11-11');
INSERT INTO t_passengers VALUES(NULL, 'shine_02','m','2019-12-12');

INSERT INTO t_passports VALUES(NULL,'China','2030-12-12',1);
INSERT INTO t_passports VALUES(NULL,'America','2035-12-12',2);

 

 

 

 

10.1、OneToOne

 

1、xml

    <!-- 结果映射(查询结果的封装规则) -->
    <resultMap id="passengerResultMap" type="com.huiruan.entity.Passengers">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="sex" column="sex"/>
        <result property="birthday" column="birthday"/>
        <!-- 关系表中数据的封装规则 -->
        <!-- 指定关系表的实体类型 -->
        <association property="passports" javaType="com.huiruan.entity.Passports">
            <id property="id" column="passenger_id"/>
            <result property="nationality" column="nationality"/>
            <result property="expire" column="expire"/>
        </association>
    </resultMap>

    <!-- 多表连接查询 -->
    <!-- 结果映射(查询结果的封装规则) -->
    <select id="selectPassengersById" resultMap="passengerResultMap">
        <!-- 别名(避免与p1.id冲突) -->
        SELECT p1.`id`,p1.`name`,p1.`sex`,p1.`birthday`,p2.`id` passport_id,
        p2.`passenger_id`,p2.`nationality`,p2.`expire`
        FROM `t_passengers` p1
        LEFT JOIN `t_passports` p2
        ON p1.`id` = p2.`passenger_id`
        WHERE p1.`id` = #{id}
    </select>

2、entity

package com.huiruan.entity;

import java.util.Date;

// 旅客
public class Passengers {
    private Integer id;
    private String name;
    private String sex;
    private Date birthday;
    // 储存护照信息
    private Passports passports;

    public Passengers() {
    }

    public Passengers(Integer id, String name, String sex, Date birthday) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.birthday = birthday;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Passengers{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                ", passports=" + passports +
                '}';
    }
}
View Code

3、接口

public Passengers selectPassengersById( @Param("id") Integer id);

注意: 指定“一方”关系时(对象),使用 <association javaType="" >

10.2、OneToMany

 1、数据库

CREATE TABLE t_departments(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50),
location VARCHAR(100)
)DEFAULT CHARSET =utf8;

CREATE TABLE t_employees (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50),
salary DOUBLE,
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES t_departments (id)
) DEFAULT CHARSET=utf8;

INSERT INTO t_departments VALUES(1,"教学部","北京"),(2,"研发部","上海");
INSERT INTO t_employees VALUES (1,"shine01",10000.5,1),(2,"shine02",20000.5,1),(3,"张三",9000.5,2),(4,"李四",8000.5,2);

2、xml

    <!-- 封装规则 -->
    <resultMap id="departmentResultMap" type="com.huiruan.entity.Departments">
        <id column="id" property="id"/>
        <result column="location" property="location"/>
        <result column="name" property="name"/>
        <!-- 关系表中数据的封装规则 -->
        <!-- 指定关系表的实体类型 -->
        <collection property="employees" ofType="com.huiruan.entity.Employees">
            <id column="emp_id" property="id"></id>
            <result column="emp_name" property="name"/>
            <result column="salary" property="salary"/>
        </collection>
    </resultMap>

    <!-- 多表连接查询 -->
    <!-- 封装规则 -->
    <select id="selectDepartmentsById" resultMap="departmentResultMap">
        <!-- 别名(避免与de.id de.name冲突) -->
        SELECT de.`id`,de.`location`,de.`name`,em.`id` emp_id,em.`name` emp_name,em.`salary`
        FROM `t_departments` de
        LEFT JOIN `t_employees` em
        ON de.`id` = em.`dept_id`
        WHERE de.`id` = 1
    </select>
View Code

 3、接口

 public List<Departments> selectDepartmentsById(Integer id);

4、entity

public class Departments {
    private Integer id;
    private String name;
    private String location;

    // 一个部门有很多员工
    private List<Employees> employees;

    public Departments() {
    }

    public Departments(Integer id, String name, String location) {
        this.id = id;
        this.name = name;
        this.location = location;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    @Override
    public String toString() {
        return "Departments{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", location='" + location + '\'' +
                ", employees=" + employees +
                '}';
    }
}
View Code

注意: 指定“多方”关系时(集合),使用 < collection ofType="" >

10.3、ManyToMany

 1、数据库创建

CREATE TABLE t_students(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50),
sex VARCHAR(1)
)DEFAULT CHARSET =utf8;

CREATE TABLE t_subjects(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50),
grade INT
)DEFAULT CHARSET=utf8;

CREATE TABLE t_stu_sub(
student_id INT,
subject_id INT,
FOREIGN KEY(student_id) REFERENCES t_students(id),
FOREIGN KEY(subject_id) REFERENCES t_subjects(id),
PRIMARY KEY (student_id, subject_id)
)DEFAULT CHARSET = utf8;

INSERT INTO t_students VALUES(1,"shine",'m'),(2,"张三",'f');
INSERT INTO t_subjects VALUES(1001,"JavaSE",1),(1002,"Javaweb",2);
INSERT INTO t_stu_sub VALUES(1,1001),(1,1002),(2,1001),(2,1002);

2、xml

    <!-- 映射查询只封装两表中的信息,可忽略关系表内容 -->
    <resultMap id="subjectsMap" type="com.huiruan.entity.Subjects">
        <id property="id" column="subid"></id>
        <result property="name" column="subname"></result>
        <result property="grade" column="grade"></result>
        <collection property="students" ofType="com.huiruan.entity.Students">
            <id property="id" column="stuid"></id>
            <result property="name" column="stuname"></result>
            <result property="sex" column="sex"></result>
        </collection>
    </resultMap>

    <!-- 三表连接查询 -->
    <select id="selectSubjectsById" resultMap="subjectsMap">
        SELECT sub.`id` subid, sub.`name` subname,sub.`grade`,
        stu.`id` stuid, stu.`name` stuname,stu.`sex` sex
        FROM `t_subjects` sub
        LEFT JOIN `t_stu_sub` ss ON sub.`id` = ss.`subject_id`  <!-- 通过t_stu_sub表建立二者之间的关系 -->
        LEFT JOIN `t_students` stu ON stu.`id` = ss.`student_id`
        WHERE sub.`id` = #{id}
    </select>

3、接口

public List<Departments> selectDepartmentsById(Integer id);

4、entity

public class Subjects {
    private Integer id;
    private String name;
    private String grade;

    // 保存学生的信息
    private List<Students> students;

    public Subjects() {
    }

    public Subjects(Integer id, String name, String grade) {
        this.id = id;
        this.name = name;
        this.grade = grade;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGrade() {
        return grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }

    @Override
    public String toString() {
        return "Subjects{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", grade='" + grade + '\'' +
                ", students=" + students +
                '}';
    }
}
View Code

指定“多方”关系时(集合),使用 <collection ofType="" >

10.4、关系总结

一方,添加集合;多方,添加对象。

双方均可建立关系属性,建立关系属性后,对应的Mapper文件中需使用< ResultMap >完成多表映射。

持有对象关系属性,使用  <association property="dept" javaType="department" >

持有集合关系属性,使用  <collection property="emps" ofType="employee" >

十一、动态sQL【重点】

MyBatis的映射文件中支持在基础SQL上添加一些逻辑操作,并动态拼接成完整的SQL之后再执行,以达到SQL复用、简化编程的效果。

11.1、sql

    <sql id="userSql"> <!-- 定义SQL片段 -->
        select `id`,`name`,`password`,`sex`,`birthday`,`registTime`
    </sql>
    <select id="selectUserById" resultType="user">
         <include refid="userSql"/> <!--通过ID引用SQL片段-->
        from t_users where id = #{arg0}
    </select>

11.2、where

    <select id="selectByAll" resultType="user">
        <include refid="userSql"/>
        from t_users
        <where>          <!-- where会自动忽略前后缀 (如:and | or) -->
            <if test="id != null">
                id = #{id}
            </if>
            <if test="name != null">
                and name = #{name}
            </if>
            <if test="password != null">
                and password = #{password}
            </if>
            <if test="sex != null">
                and sex = #{sex}
            </if>
        </where>
    </select>

11.3、set

    <update id="updateUser">
        UPDATE `t_users`
        <set>       <!-- set子句中满足条件的if,会自动忽略后缀(如:,) -->
            <if test="name != null">
                `name` = #{name},
            </if>
            <if test="password != null">
                `password` = #{password},
            </if>
            <if test="sex != null">
                `sex` = #{sex},
            </if>
            <if test="birthday != null">
                `birthday` = #{birthday},
            </if>
            <if test="registTime != null">
                `registTime` = #{registTime},
            </if>
        </set>
        WHERE id = #{id}
    </update>

11.4、trim

<trim prefix="" suffix="" prefixOverrides="" suffixOverrides="">代替<where>,<set>

    <select id="selectByAll" resultType="user">
        <include refid="userSql"/>
        from t_users
        <trim prefix="where" prefixOverrides="and|or"> <!-- 增加where前缀,自动忽略前缀 -->
            <if test="id != null">
                id = #{id}
            </if>
            <if test="name != null">
                and name = #{name}
            </if>
            <if test="password != null">
                and password = #{password}
            </if>
            <if test="sex != null">
                and sex = #{sex}
            </if>
        </trim>
    </select>
    <update id="updateUser">
        UPDATE `t_users`
        <trim prefix="set" suffixOverrides=","> <!-- 添加set前缀,自动忽略后缀(如:,) -->
            <if test="name != null">
                `name` = #{name},
            </if>
            <if test="password != null">
                `password` = #{password},
            </if>
            <if test="sex != null">
                `sex` = #{sex},
            </if>
            <if test="birthday != null">
                `birthday` = #{birthday},
            </if>
            <if test="registTime != null">
                `registTime` = #{registTime},
            </if>
        </trim>
        WHERE id = #{id}
    </update>

11.5、foreach

    <delete id="deleteInMany">
        DELETE FROM `t_users` WHERE `id` IN
        <foreach collection="list" open="(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </delete>
参数 描述 取值
collection 容器类型 list、array、map
open 起始符
close 结束符
separator 分隔符
index 下标号 从o开始,依次递增
item 当前项 任意名称(循环中通过#{任意名称}表达式访问)

十二、缓存(cache)【重点】

   内存中的一块存储空间,服务于某个应用程序,旨在将频繁读取的数据临时保存在内存中,便于二次快速访问。

 

12.1、一级缓存

  SqlSession级别的缓存,同一个SqlSession的发起多次同构查询,会将数据保存在一级缓存中

注意:无需任何配置,默认开启一级缓存

    @Test
    public void selectByAll4() {
        User user = new User();
        user.setId(5);
        SqlSession session = MyBatisUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.selectByAll(user);
        System.out.println(users);
        System.out.println("============");
        List<User> users3 = mapper.selectByAll(user);
        System.out.println(users3);

        System.out.println("============");
        User user1 = new User();
        user1.setId(5);
        SqlSession session1 = MyBatisUtils.getSession();
        UserMapper mapper1 = session1.getMapper(UserMapper.class);
        List<User> users1 = mapper1.selectByAll(user1);
        System.out.println(users1);
    }
View Code

12.2、二级缓存

  SqlSessionFactory级别的缓存,同一个SqlSessionFactory构建的SqlSession发起的多次同构查询,会将数据保存在二级缓存中。

注意: 在sqlSession.commit()或者sqlSession.close()之后生效。

12.2.1、开启全局缓存

<settings>是MyBatis中极为重要的调整设置,他们会改变MyBatis的运行行为,其他详细配置可参考官方文档。

 1、mybatis-config.xml

    <!-- 注意书写位置 -->
    <settings>
        <!--  mybatis-config.xml中开启全局缓存(默认开启) -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

12.2.2、指定Mapper缓存

    <!-- 二级缓存默认开启的,但并不是所有的查询结果,都会进入二级缓存 -->
    <cache/>
    @Test
    public void selectByAll2(){
        User user = new User();
        user.setId(5);
        SqlSession session = MyBatisUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.selectByAll(user);
        session.close(); //必须关闭SqlSession才可缓存数据
        System.out.println(users);

        User user1 = new User();
        user1.setId(5);
        SqlSession session1 = MyBatisUtils.getSession();
        UserMapper mapper1 = session1.getMapper(UserMapper.class);
        List<User> users1 = mapper1.selectByAll(user1);
        session1.close(); //必须关闭SqlSession才可缓存数据
        System.out.println(users1);
    }
View Code

12.2.3、缓存清空并重新缓存

    @Test
    public void selectByAll3(){
        User user = new User();
        user.setId(5);
        SqlSession session = MyBatisUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.selectByAll(user);
        session.close(); //必须关闭SqlSession才可缓存数据
        System.out.println(users);
        // --------------------------------

        User user3 = new User();
        user3.setSex("2");
        user3.setPassword("354354");
        user3.setId(2);
        SqlSession session3 = MyBatisUtils.getSession();
        UserMapper mapper3 = session3.getMapper(UserMapper.class);
        int i = mapper3.updateUser(user3);
        System.out.println(i);
        session3.commit();
        session3.close(); // DML成功,数据发生变化,缓存清空
        // ----------------------------------

        User user1 = new User();
        user1.setId(5);
        SqlSession session1 = MyBatisUtils.getSession();
        UserMapper mapper1 = session1.getMapper(UserMapper.class);
        List<User> users1 = mapper1.selectByAll(user1);
        session1.close(); //缓存未击中,重新查询数据库、重新缓存
        System.out.println(users1);
    }
View Code

十三、Druid连接池

13.1、概念

  Druid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求,比如向密钥服务请求凭证、统计SQL信息、SQL性能收集、SQL注入检查、SQL翻译等,程序员可以通过定制来实现自己需要的功能。

13.2、不同连接池对比

  测试执行申请归还连接1,000,000(一百万)次总耗时性能对比。

 

13.2.2、基准测试结果对比

 

13.2.3、测试结论

Druid是性能最好的数据库连接池,tomcat-jdbc和druid性能接近。

Proxool在激烈并发时会抛异常,不适用。

C3PO和Proxool都相当慢,影响sql执行效率。

BoneCP性能并不优越,采用LinkedTransferQueue并没有能够获得性能提升。

除了bonecp,其他的在JDK7上跑得比JDK6上快。

jboss-datasource虽然稳定,但性能很糟糕。

13.3、配置pom.xml

  引入Druid依赖

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

13.4、创建DruidDataSourceFactory

  MyDruidDataSourceFactory并继承PooledDataSourceFactory,并替换数据源。

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.pooled.PooledDataSourceFactory;

public class MyDruidDataSourceFactory extends PooledDataSourceFactory {
    public MyDruidDataSourceFactory(){
        this.dataSource = new DruidDataSource(); // 替换数据源
    }
}

13.5、修改mybatis-config.xml

  mybatis-config.xml中连接池相关配置。

    <environments default="MySqlDB">
        <environment id="MySqlDB">
            <transactionManager type="JDBC"></transactionManager>
            <!-- 连接池 -->
            <dataSource type="com.huiruan.utils.MyDruidDataSourceFactory"> <!-- 数据源工厂 -->
                <property name="driverClass" value="${jdbc.driver}"/>
                <property name="jdbcUrl" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

注意: <property name="属性名"/>属性名必须与com.alibaba.druid.pool.DruidAbstractDataSource中一致。

十四、PageHelper

14.1、概念

  PageHelper是适用于MyBatis框架的一个分页插件,使用方式极为便捷,支持任何复杂的单表、多表分页查询操作。

14.2、访问与下载

  官方网站: https://pagehelper.github.io/

  下载地址: https://github.com/pagehelper/Mybatis-PageHelper

14.3、开发步骤

PageHelper中提供了多个分页操作的静态方法入口。

14.3.1、引入依赖

pom.xml中引入PageHelper依赖。

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.10</version>
        </dependency>

14.3.2、配置MyBatis-config.xml

在MyBatis-config.xml中添加<plugins >。

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

14.3.3、PageHelper应用方式

使用PageHelper提供的静态方法设置分页查询条件。

    @Test
    public void selectPage() {
        User user = new User();
        SqlSession session = MyBatisUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        // 在查询前,设置分页    查询第一页,每页2条数据
        // PageHelper 对其之后的第一个查询,进行分页功能追加
        PageHelper.startPage(2,2);
        List<User> users = mapper.selectByAll(user);
        System.out.println(users);

        // 将查询结果 封装到PageInfo 对象中
        PageInfo pageInfo = new PageInfo(users);
        System.out.println(pageInfo);
    }

14.4、Pagelnfo对象

  Pagelnfo对象中包含了分页操作中的所有相关数据。

 

14.4.1、注意事项

只有在PageHelper.startPage()方法之后的第一个查询会有执行分页

分页插件不支持带有"for update”的查询语句。

分页插件不支持“嵌套查询”,由于嵌套结果方式会导致结果集被折叠,所以无法保证分页结果数量正确。。

十五、补充【了解】

  以下内容并非必备知识,了解即可。

15.1、MyBatis注解操作

  通过在接口中直接添加MyBatis注解,完成CRUD。

注意:接口注解定义完毕后,需将接口全限定名注册到mybatis-config.xml的< mappers >中

经验︰注解模式属于硬编码到.java文件中,失去了使用配置文件外部修改的优势,可结合需求选用。

    <mappers>
        <!-- class="接口全限定名" -->
        <mapper class="com.huiruan.mapper.UserMapper"/>
    </mappers>

15.1.1、查询

    @Select("select * from t_users where id = #{id}")
    public User selectUserById(Integer id);

    @Select("select * from t_users where id = #{id} and password = #{pwd}")
    public User selectUserByUserInfo(@Param("id") Integer id,@Param("pwd")String pwd);

15.1.2、删除

    @Delete("DELETE FROM t_users WHERE id = #{id}")
    public int deleteById(@Param("id") Integer id);

15.1.3、修改

    @Update("update t_users set name = #{name} where id = #{id}")
    public int updateUser(User user);

15.1.4、插入

    @Insert("INSERT INTO `t_users`(`id`,`name`,`password`,`sex`,`birthday`,`registTime`) " +
            "VALUE (#{id},#{name},#{password},#{sex},#{birthday},#{registTime})")
    public int insertUser(User user);

    @Options(useGeneratedKeys = true ,keyProperty = "id")
    @Insert("INSERT INTO `t_users`(`id`,`name`,`password`,`sex`,`birthday`,`registTime`) " +
            "VALUE (#{id},#{name},#{password},#{sex},#{birthday},#{registTime})")
    public int insertUserGeneratedKeys(User user);

15.2、$符号的应用场景

  ${attribute}属于字符串拼接SQL,而非预编译占位符,会有注入攻击问题,不建议在常规SQL中使用,常用于可解决动态升降序问题。

15.2.1、$符号参数绑定

    // ${name} ${id} 可获取user中的属性值
    public List<User> selectAllUser(User user);

    // 必须使用@param 否则会作为属性解析
    public List<User> selectUserByRule(@Param("rule") String rule);
    <!-- 拼接name和id,如果是字符类型需要用单引号:'${name}'  -->
    <select id="selectAllUser" resultType="user">
        SELECT * FROM `t_users` WHERE `name` = '${name}' OR `id` = ${id}
    </select>

    <!-- 拼接 asc | desc -->
    <select id="selectUserByRule" resultType="user">
        SELECT * FROM `t_users` ORDER BY id ${rule}
    </select>
    @Test
    public void selectAllUser(){
        User user = new User();
        user.setId(3);
        user.setName("laoli");
        UserDao mapper = MyBatisUtils.getMapper(UserDao.class);
        // 调用时传入user对象
        List<User> users = mapper.selectAllUser(user);
        System.out.println(users);
    }

    @Test
    public void selectUserByRule(){
        UserDao mapper = MyBatisUtils.getMapper(UserDao.class);
        // 调用时传入 asc | desc
        List<User> desc = mapper.selectUserByRule("asc");
        System.out.println(desc);
    }

15.2.2、$符号注入攻击

    <!-- 会存在注入攻击比如传入参数是[String name = "laoli' or '1'='1"] -->
    <select id="selectUserImmit" resultType="user">
        select * from t_users where name = '${name}'
    </select>
public List<User> selectUserImmit(@Param("name") String name);
    @Test
    public void selectUserImmit(){
        UserDao mapper = MyBatisUtils.getMapper(UserDao.class);
        // 调用时传入 asc | desc
        List<User> desc = mapper.selectUserImmit("laoli' or '1' = '1");
        System.out.println(desc);
    }

15.3、MyBatis处理关联关系-嵌套查询【了解】

思路:查询部门信息时,及联查询所属的员工信息。

  DepartmentsMapper接口中定义selectDepartmentsById,并实现Mapper。

  EmployeesMapper接口中定义selectEmployeesByDepartId,并实现Mapper,

  当selectDepartmentsById被执行时,通过< collection>调用selectEmployeesByDepartId方法,并传入条件参数。

15.3.1、主表查询

定义selectEmployeesByDepartId,并书写Mapper,实现根据部门ID查询员工信息

public interface EmployeesMapper {
    /**
     * 根据部门编号查询员工信息
     * @param id 部门编号
     * @return 该部门中的所有员工
     */
    public List<Employees> selectEmployeesByDepartId(Integer id);
}
<mapper namespace="com.huiruan.mapper.EmployeesMapper">
    <!-- 根据部门编号查询所有员工 -->
    <select id="selectEmployeesByDepartId" resultType="com.huiruan.entity.Employees">
        select * from t_employees where dept_id = #{id}
    </select>
</mapper>

15.3.2、及联调用

  定义selectDepartmentsById,并书写Mapper,实现根据部门ID查询部门信息,并及联查询该部门员工信息

    /**
     * 查询部门信息
     * @param id 部门
     * @return
     */
    public List<Departments> selectDepartmentsById(Integer id);
    <resultMap id="departmentResultMap" type="com.huiruan.entity.Departments">
        <id column="id" property="id"/>
        <result column="location" property="location"/>
        <result column="name" property="name"/>
        <!-- column = "传入目标方法的条件参数" select="及联调用的查询目标" -->
        <collection property="employees" ofType="com.huiruan.entity.Employees" column="id"
                    select="com.huiruan.mapper.EmployeesMapper.selectEmployeesByDepartId"/>
    </resultMap>


    <select id="selectDepartmentsById" resultMap="departmentResultMap">
        SELECT de.`id`,de.`location`,de.`name`
        FROM `t_departments` de
        WHERE de.`id` = #{id}
    </select>

测试

    @Test
    public void selectDepartmentsById(){
        DepartmentsMapper mapper = MyBatisUtils.getMapper(DepartmentsMapper.class);
        List<Departments> departments = mapper.selectDepartmentsById(1);
        System.out.println(departments);
    }

15.3.3、延迟加载

  mybatis-config.xml中开启延迟加载

    <!-- 注意书写位置 -->
    <settings>
        <!--  mybatis-config.xml中开启全局缓存(默认开启) -->
        <setting name="cacheEnabled" value="true"/>
        <!-- 开启延时加载 (默认false) -->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>

注意︰开启延迟加载后,如果不使用及联数据,则不会触发及联查询操作,有利于加快查询速度、节省内存资源。

posted @ 2021-08-21 23:31  菜鸟的道路  阅读(89)  评论(0)    收藏  举报