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>
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>
注意:
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;
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 + '}'; } }
5.3、定义DAO接口
根据所需DA0定义接口、以及方法
public interface UserMapper { public User selectUserById(Integer id); }
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>
5.5、注册Mapper
将Mapper.xml注册到mybatis-config.xml中
<!--Mapper注册--> <mappers> <!--注册Mapper文件的所在位置--> <mapper resource="UserMapper.xml"/> </mappers>
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)); } }
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); }
六、细节补充
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>
修改mybatis-config.xml
<!--Mapper注册--> <mappers> <!--注册Mapper文件的所在位置--> <mapper resource="com/huiruan/mapper/UserMapper.xml"/> </mappers>
6.2、properties配置文件
对于mybatis-config.xml的核心配置中,如果存在需要频繁改动的数据内容,可以提取到properties中。
#jdbc.properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/huiruan?useUnicode=true&characterEncoding=utf-8 jdbc.username=root jdbc.password=root
修改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}"/> <!--&转义&--> <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>
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>
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(); } }
九、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 + '}'; } }
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>
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 + '}'; } }
注意: 指定“多方”关系时(集合),使用 < 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 + '}'; } }
指定“多方”关系时(集合),使用 <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); }
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); }
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); }
十三、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>
注意︰开启延迟加载后,如果不使用及联数据,则不会触发及联查询操作,有利于加快查询速度、节省内存资源。

浙公网安备 33010602011771号