Mybatis

返回Map类型的结果

mybatis会将查询到的记录先转换成对应的Map 对象(以字段名作为key,以字段值作为value。 一条记录对应一个Map)。然后再将Map中的数据 存放到对应的实体对象里面。

解决实体类的属性与表的字段名不一致

方式一:字段使用别名 
方式二: 使用resultMap元素。

如何使用Mapper映射器?

调用SqlSession的getMapper方法,获得符合映射器 要求的对象即可。

    1. atis介绍

简介

MyBatis 是一款支持普通SQL查询、存储过程以及高级映射的优秀的持久层ORM框架。

MyBatis 使用简单的 XML 或注解来配置和定义映射关系,将 Java 的 POJO映射成数据库中的记录。

MyBatis 封装了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索,开发者只需要关注 SQL 本身。

ORM是什么

Object Relational Mapping,对象关系映射。即建立实体类和数据库表之间的对应关系,通过操作实体类来操作数据库表。

历史

iBatis由Clinton Begin 在2002 年创建,其后,捐献给了Apache基金会,成立了iBatis 项目。2010 年5 月,将代码库迁致Google Code,并更名为MyBatis。后来又迁移至GitHub。

资源

官方中文文档:mybatis – MyBatis 3 | 简介 

jar包下载地址:Releases · mybatis/mybatis-3 · GitHub 

体系结构

映射文件.xml ——→ 总配置文件SqlMapConfig.xml ←—— 映射文件.xml

SqlSessionFactoryBuilder

SqlSessionFactory

SqlSession

Executor

sql输入映射参数(基本数据、HashMap、POJO)——→ Mapped Statement ←—— sql输出映射参数(基本数据、HashMap、POJO)

数据库

SqlMapConfig.xml(1个):主配置文件,用于指定框架参数和数据库连接参数。

mapper.xml文件(多个):映射定义文件,用于定义SQL语句和输入输出参数的映射信息。此文件需在SqlMapConfig.xml中加载。

SqlSessionFactoryBuilder负责根据主配置文件构建SqlSessionFactory实例。

SqlSessionFactory每一个MyBatis应用程序都以一个SqlSessionFactory对象为核心。该对象负责创建SqlSession对象实例。

SqlSession该对象包含了执行SQL语句的增删改查方法。

ExecutorMyBatis底层的执行器接口,用于将SQL的入参映射至SQL中(相当于jdbc中对preparedStatement设置参数),将结果映射至JavaBean中(相当于jdbc中解析处理结果集)。有两个实现,一个是基本执行器、一个是缓存执行器。

Mapped StatementMyBatis的底层封装对象,包装了SQL语句、入参映射信息和结果集映射信息。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

处理流程

加载配置

配置有两种形式,一种是XML配置文件,另一种是Java代码的注解。MyBatis将SQL的配置信息加载成为一个个的MappedStatement对象(包含传入参数映射配置、执行的SQL语句、结果映射配置),并将其存储在内存中。

SQL解析

当API接口层接收到调用请求时,会接收到传入SQL的ID和传入参数对象(可以是Map、javaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。

SQL执行

将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。

结果映射

将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap。JavaBean或基本数据类型,并将最终结果返回。

与Hibernate比较

MyBatis

Hibernate

学习成本

MyBatis简单易学(特别是有SQL语法基础的人),较接近JDBC

Hibernate的配置复杂精深,学习成本高。

程序灵活性

MyBatis半自动映射,直接编写SQL,可严格控制sql执行性能,灵活性高

Hibernate全表映射经常增加不需要的数据传输, 比如更新时需要发送所有字段;

无法根据不同的条件组装不同的SQL;

对多表关联和复杂SQL查询支持较差,需要自己写SQL,返回后,需要自己将数据组装为POJO;

不能有效支持存储过程;

HQL性能较差,调优困难,不利于大型互联网系统的SQL级别优化。

程序执行效律

MyBatis效律高

可移植性

差。不同数据库需写不同SQL

好。与数据库关联在配置中完成,HQL语句与数据库无关

适用场合

互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速

微服务中,因为微服务把复杂关系拆开,减少了关联查询。

    • DAO接口的MyBatis开发步骤

导包

MyBatis的jar包下载地址:Releases · mybatis/mybatis-3 · GitHub 解压后:

mybatis-3.2.7.jar----mybatis的核心包

lib----mybatis的依赖包

mybatis-3.2.7.pdf----mybatis使用手册

导入mybatis核心包和依赖包、数据库驱动包、日志包(配置日志配置文件):

asm-3.3.1.jar

cglib-2.2.2.jar

commons-logging-1.1.1.jar

javassist-3.17.1-GA.jar

junit-4.9.jar

log4j-1.2.17.jar

log4j-api-2.0-rc1.jar

log4j-core-2.0-rc1.jar

mybatis-3.2.7.jar

mysql-connector-java-5.1.7-bin.jar

slf4j-api-1.7.5.jar

slf4j-log4j12-1.7.5.jar

在classpath下创建日志文件log4j.properties(mybatis默认使用log4j作为输出日志信息)

# Global logging configuration

# DEBUG for development environment, INFO or ERROR for production environment

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

在classpath下创建主配置文件(名称可自定义,内容可参考用户手册)。

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>

<!-- ★★★★★★★★★★设置别名★★★★★★★★★★ -->

<!--

通过别名简化对类的使用:

内置别名:对常用的java类型,已经内置了一些别名支持。这些别名都是不区分大小写的。(详细参看用户手册)

自定义别名:在myBatis的主配置文件给pojo.Dept类创建别名Dept,后继的DeptMapper.xml配置文件中可以使用该别名

-->

<typeAliases>

<typeAlias type="pojo.Dept" alias="Dept" />

</typeAliases>

<!-- ★★★★★★★★★★设置运行环境★★★★★★★★★★ -->

<!-- 可以设置多个运行环境,满足不同需要,例如开发、测试、生产。和spring整合后 environments配置将废除。 -->

<environments default="development">

<environment id="development">

<!-- 配置事务管理。类型主要有jdbc和managed,前者依赖于数据源获得的连接,后者依赖于容器 -->

<transactionManager type="JDBC" />

<!-- 配置数据库连接池 -->

<dataSource type="POOLED">

<property name="driver" value="com.mysql.jdbc.Driver" />

<!-- 如果数据库设置为UTF-8,则URL参数连接需要添加?useUnicode=true&characterEncoding=UTF-8,如下 -->

<property name="url"

value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8" />

<property name="username" value="root" />

<property name="password" value="root" />

</dataSource>

</environment>

</environments>

<!-- ★★★★★★★★★★引用映射文件★★★★★★★★★★ -->

<mappers>

</mappers>

</configuration>

创建数据库及表

drop database if exists mybatis;

create database mybatis CHARACTER SET UTF8;

use mybatis;

create table dept(

    dept_id int primary key auto_increment,

    dept_name varchar(50),

dept_address varchar(50),

dept_uuid varchar(36)

);

insert into dept(dept_name,dept_address) values('研发部一部','广州');

insert into dept(dept_name,dept_address) values('研发部二部','广州');

insert into dept(dept_name,dept_address) values('研发部三部','深圳');

select * from dept;

创建实体类(类名和属性与数据库表名和字段名相呼应)

Dept.java:

package pojo;

import java.io.Serializable;

public class Dept implements Serializable {

private Integer deptId; //部门编号

private String deptName;//部门名称

private String deptAddress;//部门地址

private String deptUuid;//测试mysql的UUID主键返回

省略getter、setter和toString方法

}

创建SQL映射文件,实现增删改查SQL

实体类属性与表字段名不一致解决方法:字段使用别名,或者使用resultMap元素。

Dept.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:命名空间,用于隔离sql语句 -->

<mapper namespace="mapper.DeptMapper">

<!--

id属性:定义sql语句的位置,java代码中通过命名空间+id方式找到该语句。

parameterType属性:定义输入参数映射的Java类型。若不指定,Mybatis也能自动根据代码传递的参数Dept自动匹配。

主配置文件未设置别名时,parameterType指定的pojo需写全名。

ognl表达式:

#{变量名}表示占位符,接收简单类型值或pojo属性值。输入参数为简单类型时,变量名可任意。

${变量名}表示拼接符,接收简单类型值或pojo属性值。输入参数为简单类型时,变量名只能为value。

${变量名}不建议使用,有SQL注入问题。

resultType属性:定义输出结果映射的Java类型。

-->

<!-- ★★★★★★★★★★ 增(mysql返回自增主键值) ★★★★★★★★★★ -->

<insert id="insert" parameterType="Dept">

<!--

selectKey可实现主键返回

keyProperty:指定返回的主键存储在pojo中的哪个属性

order:指定selectKey语句相对于insert语句的执行顺序。

resultType:返回的主键是什么类型

LAST_INSERT_ID():是mysql的函数,返回auto_increment自增列新记录的id值

由于mysql执行完insert语句之后才生成自增主键,所以这里的order值应设为AFTER

-->

<selectKey keyProperty="deptId" order="AFTER" resultType="java.lang.Integer">

select LAST_INSERT_ID()

</selectKey>

insert into dept(dept_name,dept_address) values(#{deptName},#{deptAddress})

</insert>

<!-- ★★★★★★★★★★ 增(mysql返回UUID主键值) ★★★★★★★★★★ -->

<insert id="insert2" parameterType="Dept">

<!--

select uuid():是mysql的函数,返回UUID值

注意这里的order值应设为BEFORE

-->

<selectKey keyProperty="deptUuid" order="BEFORE" resultType="java.lang.String">

select uuid()

</selectKey>

insert into dept(dept_uuid,dept_name,dept_address) values(#{deptUuid},#{deptName},#{deptAddress})

</insert>

<!-- ★★★★★★★★★★ 增(Oracle返回自定义序列生成的主键值) ★★★★★★★★★★ -->

<insert id="insert3" parameterType="Dept">

<!-- 注意这里的order值应设为BEFORE -->

<selectKey keyProperty="deptId" order="BEFORE" resultType="java.lang.Integer">

SELECT 自定义序列.NEXTVAL FROM DUAL

</selectKey>

insert into dept(dept_id,dept_name,dept_address) values(#{deptId},#{deptName},#{deptAddress})

</insert>

<!-- ★★★★★★★★★★ 删 ★★★★★★★★★★ -->

<delete id="delete" parameterType="Dept">

delete from dept where

dept_id=#{deptId}

</delete>

<!-- ★★★★★★★★★★ 改 ★★★★★★★★★★ -->

<update id="update" parameterType="Dept">

update dept set

dept_name=#{deptName}, dept_address=#{deptAddress} where

dept_id=#{deptId}

</update>

<!-- ★★★★★★★★★★ 显示指定表字段实体属性的映射关系,两者命名不一致时配置,用于查询。 ★★★★★★★★★★ -->

<!-- 可在SQL语句中使用字段别名代替此配置 -->

<resultMap id="deptResultMap" type="Dept">

<id property="deptId" column="dept_id" />

<result property="deptName" column="dept_name" />

<result property="deptAddress" column="dept_address" />

</resultMap>

<!-- ★★★★★★★★★★ 查单条 ★★★★★★★★★★ -->

<!-- 表字段和对应实体属性命名一致时可以不引用resultMap配置,直接使用resultType="返回的全类名或别名",建议使用前者;查询结果为所有字段时,也可以用*表示 -->

<select id="selectOne" parameterType="int" resultMap="deptResultMap">

select

dept_id, dept_name from dept where dept_id=#{deptId}

</select>

<!-- ★★★★★★★★★★ 查多条 ★★★★★★★★★★ -->

<!-- 表字段和对应实体属性命名一致时可以不引用resultMap配置,直接使用resultType="返回的全类名或别名",建议使用前者;查询结果为所有字段时,也可以用*表示 -->

<!-- 返回结果配置的不是集合类型,而是集合元素的类型;参数也可以通过Map等方式封装(Java代码中用(Map)强转即可)  -->

<select id="selectList" parameterType="Map" resultMap="deptResultMap">

select *

from dept where dept_name like #{deptName}

</select>

</mapper>

修改主配置文件,指定映射文件的位置

myBatis-config.xml:

<?xml version="1.0" encoding="UTF-8" ?> 

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 

"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

…………

<environments default="development">

…………

</environments>

<!-- ★★★★★★★★★★引用映射文件★★★★★★★★★★ -->

<mappers>

<mapper resource="mapper/DeptMapper.xml" /> <!-- 原始DAO开发Mapper文件 -->

</mappers>

</configuration>

编写DAO接口和实现类使用myBatis提供的API来访问数据库

接口DeptDao:

package dao;

import java.util.List;

import java.util.Map;

import pojo.Dept;

public interface DeptDao {

public int insert(Dept dept);

public int delete(Dept dept);

public int update(Dept dept);

public Dept selectOne(int deptId);

public List<Dept> selectList(Map map);

}

实现类:DeptDaoImpl.java(原始版,展示流程)

import pojo.Dept;

/**

 * 用于插入数据到dept表。

 * @param dept 部门信息

 * @return 表示受影响的行数

 */

public class DeptDaoImpl {

public int insert(Dept dept) {

int i = 0;

SqlSession sqlSession = null;

String config = "myBatis-config.xml";

Reader reader = null;

try {

/*

 * 获取SqlSession对象

 */

//1.读取配置信息

reader = Resources.getResourceAsReader(config);

//2.构建session工厂

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

//3.创建session

sqlSession = sqlSessionFactory.openSession();

/*

 * 使用SqlSession对象进行增删改查操作

 */

//4.事务默认自动启动

//5.处理数据。SQL映射语句的位置为该语句所在的namespace+该语句的id

i = sqlSession.insert("mapper.DeptMapper.insert", dept);

//6.提交事务

sqlSession.commit();

} catch (Exception e) {

e.printStackTrace();

//6.若失败则回滚事务

sqlSession.rollback();

} finally {

try {

//7.关闭文件流

reader.close();

} catch (IOException e) {

e.printStackTrace();

}

//8.关闭session

sqlSession.close();

}

return i;

}

}

可提取出MyBatisUtil工具类:

package util;

import java.io.IOException;

import java.io.Reader;

import org.apache.ibatis.io.Resources;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisUtil {

private static final ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();

private static SqlSessionFactory sessionFactory;

private static String CONFIG_FILE_LOCATION = "myBatis-config.xml";

static {

try {

buildSessionFactory();

} catch (Exception e) {

System.err.println("%%%% Error Creating SessionFactory %%%%");

e.printStackTrace();

}

}

private MyBatisUtil() {

}

/**

 * Returns the ThreadLocal Session instance.  Lazy initialize

 * the <code>SessionFactory</code> if needed.

 *

 *  @return Session

 *  @throws Exception

 */

public static SqlSession getSession() throws Exception {

SqlSession session = threadLocal.get();

if (session == null) {

if (sessionFactory == null) {

buildSessionFactory();

}

session = (sessionFactory != null) ? sessionFactory.openSession() : null;

threadLocal.set(session);

}

return session;

}

/**

 *  创建session工厂

 */

public static void buildSessionFactory() {

Reader reader = null;

try {

//读取配置信息

reader = Resources.getResourceAsReader(CONFIG_FILE_LOCATION);

//构建session工厂

sessionFactory = new SqlSessionFactoryBuilder().build(reader);

} catch (Exception e) {

System.err.println("%%%% Error Creating SessionFactory %%%%");

e.printStackTrace();

} finally {

try {

//关闭输入字符流

reader.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

/**

 *  关闭session单例

 */

public static void closeSession() {

SqlSession session = threadLocal.get();

threadLocal.set(null);

if (session != null) {

session.close();

}

}

/**

 *  获取session工厂

 */

public static SqlSessionFactory getSessionFactory() {

return sessionFactory;

}

}

重新编写实现类DeptDaoImpl.java:

注意:

SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。

SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。

每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。

打开一个 SqlSession,使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。

package daoimpl;

import java.util.List;

import java.util.Map;

import org.apache.ibatis.session.SqlSession;

import dao.DeptDao;

import pojo.Dept;

import util.MyBatisUtil;

public class DeptDaoImpl implements DeptDao {

/**

 * 增

 */

public int insert(Dept dept) {

SqlSession session = null;

int i = 0;

try {

//获取sqlSession

session = MyBatisUtil.getSession();

//事务默认自动启动

//处理数据。SQL映射语句的位置为该语句所在的namespace+该语句的id

i = session.insert("mapper.DeptMapper.insert", dept);

//i = session.insert("mapper.DeptMapper.insert2", dept);

//i = session.insert("mapper.DeptMapper.insert3", dept);

//提交事务

session.commit();

} catch (Exception e) {

e.printStackTrace();

//失败则回滚事务

session.rollback();

} finally {

//关闭sqlSession

MyBatisUtil.closeSession();

}

return i;

}

/**

 * 删

 */

public int delete(Dept dept) {

SqlSession session = null;

int i = 0;

try {

session = MyBatisUtil.getSession();

//SQL映射语句的位置为该语句所在的namespace+该语句的id

i = session.delete("mapper.DeptMapper.delete", dept);

//System.out.println("受影响行数:"+i);

session.commit();

} catch (Exception e) {

e.printStackTrace();

session.rollback();

} finally {

MyBatisUtil.closeSession();

}

return i;

}

/**

 * 改

 */

public int update(Dept dept) {

SqlSession session = null;

int i = 0;

try {

session = MyBatisUtil.getSession();

//SQL映射语句的位置为该语句所在的namespace+该语句的id

i = session.update("mapper.DeptMapper.update", dept);

System.out.println("受影响行数:" + i);

session.commit();

} catch (Exception e) {

e.printStackTrace();

session.rollback();

} finally {

MyBatisUtil.closeSession();

}

return i;

}

/**

 * 查单条

 */

public Dept selectOne(int deptId) {

SqlSession session = null;

Dept dept = null;

try {

session = MyBatisUtil.getSession();

//selectOne只能查询一条记录,如果使用selectOne查询多条记录则抛异常

//SQL映射语句的位置为该语句所在的namespace+该语句的id

dept = (Dept) session.selectOne("mapper.DeptMapper.selectOne", deptId);

System.out.println("dept:" + dept);

} catch (Exception e) {

e.printStackTrace();

} finally {

MyBatisUtil.closeSession();

}

return dept;

}

/**

 * 查多条

 */

public List<Dept> selectList(Map map) {

SqlSession session = null;

List<Dept> depts = null;

try {

session = MyBatisUtil.getSession();

//selectList可以查询一条或多条记录

//SQL映射语句的位置为该语句所在的namespace+该语句的id

depts = session.selectList("mapper.DeptMapper.selectList", map);

} catch (Exception e) {

e.printStackTrace();

} finally {

MyBatisUtil.closeSession();

}

return depts;

}

}

编写测试类

需要导入junit包

package daoimpl;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import org.junit.AfterClass;

import org.junit.BeforeClass;

import org.junit.Test;

import pojo.Dept;

public class DeptDaoImplTest {

private static DeptDaoImpl deptDaoImpl;

@BeforeClass

public static void setUpBeforeClass() throws Exception {

deptDaoImpl = new DeptDaoImpl();

}

@AfterClass

public static void tearDownAfterClass() throws Exception {

deptDaoImpl = null;

}

@Test

public void testInsert() {

Dept dept = new Dept();

dept.setDeptName("市场部");

dept.setDeptAddress("深圳");

int i = deptDaoImpl.insert(dept);

System.out.println("受影响行数:" + i);

System.out.println("dept自增主键值:" + dept.getDeptId());

System.out.println("dept的UUID主键值:" + dept.getDeptUuid());

}

@Test

public void testDelete() {

Dept dept = new Dept();

dept.setDeptId(4);

int i = deptDaoImpl.delete(dept);

System.out.println("受影响行数:" + i);

}

@Test

public void testUpdate() {

Dept dept = new Dept();

dept.setDeptId(1);

dept.setDeptName("研发部总部");

dept.setDeptAddress("北京");

int i = deptDaoImpl.update(dept);

System.out.println("受影响行数:" + i);

}

@Test

public void testSelectOne() {

int id = 1;

Dept dept = deptDaoImpl.selectOne(id);

System.out.println(dept);

}

@Test

public void testSelectList() {

Map map = new HashMap();

map.put("deptName", "%研%");

List<Dept> depts = deptDaoImpl.selectList(map);

for (Dept dept : depts) {

System.out.println("dept:" + dept);

}

}

}

基于Mapper接口的MyBatis开发步骤

原理

Mapper接口开发方式只需要程序员编写Mapper接口(相当于Dao接口,又叫Mapper映射器)和Mapper文件,由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

Mapper接口代理对象的获取方式(例):DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);

条件

Mapper接口开发需要遵循以下规范:

Mapper接口的类路径(完全限定名)和Mapper文件中的namespace相同。

Mapper接口方法名和Mapper文件中定义的每个statement的id相同

Mapper接口方法的参数类型和Mapper文件中定义的每个statement的parameterType的类型相同

Mapper接口方法的返回值类型和Mapper文件中定义的每个statement的resultType的类型相同

导包、创建日志文件、创建主配置文件、创建数据库及表、创建实体类

方法同前

创建SQL映射文件,实现增删改查SQL

实体类属性与表字段名不一致解决方法:字段使用别名,或者使用resultMap元素。

/mybatis/src/main/resources/mapper/DeptMapper.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接口的类路径相同 -->

<mapper namespace="dao.DeptMapper">

<!--

id属性:与对应Mapper接口的方法名相同

parameterType属性:与对应Mapper接口的方法参数类型相同

ognl表达式:

#{变量名}表示占位符,接收简单类型值或pojo属性值。输入参数为简单类型时,变量名可任意。

${变量名}表示拼接符,接收简单类型值或pojo属性值。输入参数为简单类型时,变量名只能为value。

${变量名}不建议使用,有SQL注入问题。

resultType属性:与对应Mapper接口的方法返回值类型相同

-->

<!-- ★★★★★★★★★★ 增(mysql返回自增主键值) ★★★★★★★★★★ -->

<insert id="saveDept" parameterType="Dept">

<!--

selectKey可实现主键返回

keyProperty:指定返回的主键存储在pojo中的哪个属性

order:指定selectKey语句相对于insert语句的执行顺序。

resultType:返回的主键是什么类型

LAST_INSERT_ID():是mysql的函数,返回auto_increment自增列新记录的id值

由于mysql执行完insert语句之后才生成自增主键,所以这里的order值应设为AFTER

-->

<selectKey keyProperty="deptId" order="AFTER" resultType="java.lang.Integer">

select LAST_INSERT_ID()

</selectKey>

insert into dept(dept_name,dept_address) values(#{deptName},#{deptAddress})

</insert>

<!-- ★★★★★★★★★★ 增(mysql返回UUID主键值) ★★★★★★★★★★ -->

<insert id="saveDept2" parameterType="Dept">

<!--

select uuid():是mysql的函数,返回UUID值

注意这里的order值应设为BEFORE

-->

<selectKey keyProperty="deptUuid" order="BEFORE" resultType="java.lang.String">

select uuid()

</selectKey>

insert into dept(dept_uuid,dept_name,dept_address)

<!-- 插入数据时,可指定JdbcType以防止插入空值时引发类型转换错误 -->

values(#{deptUuid},#{deptName,jdbcType=VARCHAR},#{deptAddress,jdbcType=VARCHAR})

</insert>

<!-- ★★★★★★★★★★ 增(Oracle返回自定义序列生成的主键值) ★★★★★★★★★★ -->

<insert id="saveDept3" parameterType="Dept">

<!-- 注意这里的order值应设为BEFORE -->

<selectKey keyProperty="deptId" order="BEFORE" resultType="java.lang.Integer">

SELECT 自定义序列.NEXTVAL FROM DUAL

</selectKey>

insert into dept(dept_id,dept_name,dept_address)

<!-- 插入数据时,可指定JdbcType以防止插入空值时引发类型转换错误 -->

values(#{deptId},#{deptName,jdbcType=VARCHAR},#{deptAddress,jdbcType=VARCHAR})

</insert>

<!-- ★★★★★★★★★★ 删 ★★★★★★★★★★ -->

<delete id="deleteDept" parameterType="Dept">

delete from dept where

dept_id=#{deptId}

</delete>

<!-- ★★★★★★★★★★ 改 ★★★★★★★★★★ -->

<update id="updateDept" parameterType="Dept">

update dept set

dept_name=#{deptName}, dept_address=#{deptAddress} where

dept_id=#{deptId}

</update>

<!-- ★★★★★★★★★★显示指定表字段实体属性的映射关系,两者命名不一致时配置,用于查询。 ★★★★★★★★★★ -->

<!-- 可在SQL语句中使用字段别名代替此配置 -->

<resultMap id="deptResultMap" type="Dept">

<id property="deptId" column="dept_id" />

<result property="deptName" column="dept_name" />

<result property="deptAddress" column="dept_address" />

</resultMap>

<!-- ★★★★★★★★★★ 查单条 ★★★★★★★★★★ -->

<!-- 表字段和对应实体属性命名一致时可以不引用resultMap配置,直接使用resultType="返回的全类名或别名",建议使用前者;查询结果为所有字段时,也可以用*表示 -->

<select id="findDeptById" parameterType="int" resultMap="deptResultMap">

select

dept_id, dept_name from dept where dept_id=#{deptId}

</select>

<!-- ★★★★★★★★★★ 查多条 ★★★★★★★★★★ -->

<!-- 表字段和对应实体属性命名一致时可以不引用resultMap配置,直接使用resultType="返回的全类名或别名",建议使用前者;查询结果为所有字段时,也可以用*表示 -->

<!-- 返回结果配置的不是集合类型,而是集合元素的类型;参数也可以通过Map等方式封装(Java代码中用(Map)强转即可)  -->

<select id="findDeptByName" parameterType="Map" resultMap="deptResultMap">

select *

from dept where dept_name like #{deptName}

</select>

</mapper>

修改主配置文件,加入映射文件信息

……

<!-- ★★★★★★★★★★引用映射文件★★★★★★★★★★ -->

<mappers>

<mapper resource="mapper/Dept.xml" /> <!-- 原始DAO开发Mapper文件 -->

<mapper resource="mapper/DeptMapper.xml" /> <!-- 新式Mapper接口开发Mapper文件 -->

</mappers>

……

编写Mapper接口

/mybatis/src/main/java/dao/DeptMapper.java:

package dao;

import java.util.List;

import java.util.Map;

import pojo.Dept;

public interface DeptMapper {

public int saveDept(Dept dept);

public int saveDept2(Dept dept);

public int saveDept3(Dept dept);

public int deleteDept(Dept dept);

public int updateDept(Dept dept);

public Dept findDeptById(int deptId);

public List<Dept> findDeptByName(Map map);

}

编写测试类

/mybatis/src/main/java/dao/DeptMapperTest.java:

package dao;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import org.apache.ibatis.session.SqlSession;

import org.junit.Test;

import pojo.Dept;

import util.MyBatisUtil;

public class DeptMapperTest {

@Test

public void testSaveDept() {

//获取session

SqlSession session = null;

try {

session = MyBatisUtil.getSession();

//获取mapper接口的代理对象

DeptMapper deptMapper = session.getMapper(DeptMapper.class);

//要添加的数据

Dept dept = new Dept();

dept.setDeptName("市场部");

dept.setDeptAddress("深圳");

//调用代理对象的方法

int i = deptMapper.saveDept2(dept);

//提交事务

session.commit();

System.out.println("受影响行数:" + i);

System.out.println("dept自增主键值:" + dept.getDeptId());

System.out.println("dept的UUID主键值:" + dept.getDeptUuid());

} catch (Exception e) {

e.printStackTrace();

//失败则回滚事务

session.rollback();

} finally {//关闭session

MyBatisUtil.closeSession();

}

}

@Test

public void testDeleteDept() {

SqlSession session = null;

try {

//获取session

session = MyBatisUtil.getSession();

//获取mapper接口的代理对象

DeptMapper deptMapper = session.getMapper(DeptMapper.class);

//要删除的数据

Dept dept = new Dept();

dept.setDeptId(5);

//调用代理对象的方法

int i = deptMapper.deleteDept(dept);

//提交事务

session.commit();

System.out.println("受影响行数:" + i);

} catch (Exception e) {

e.printStackTrace();

//失败则回滚事务

session.rollback();

} finally {

//关闭session

MyBatisUtil.closeSession();

}

}

@Test

public void testUpdateDept() {

SqlSession session = null;

try {

//获取session

session = MyBatisUtil.getSession();

//获取mapper接口的代理对象

DeptMapper deptMapper = session.getMapper(DeptMapper.class);

//要更新的数据

Dept dept = new Dept();

dept.setDeptId(1);

dept.setDeptName("研发部总部");

dept.setDeptAddress("北京");

//调用代理对象的方法

int i = deptMapper.updateDept(dept);

//提交事务

session.commit();

System.out.println("受影响行数:" + i);

} catch (Exception e) {

e.printStackTrace();

//失败则回滚事务

session.rollback();

} finally {

//关闭session

MyBatisUtil.closeSession();

}

}

@Test

public void testFindDeptById() {

SqlSession session = null;

try {

//获取session

session = MyBatisUtil.getSession();

//获取mapper接口的代理对象

DeptMapper deptMapper = session.getMapper(DeptMapper.class);

调用代理对象的方法,由于要返回单个POJO对象,动态代理对象将调用sqlSession.selectOne()方法

Dept dept = deptMapper.findDeptById(1);

System.out.println(dept);

} catch (Exception e) {

e.printStackTrace();

} finally {

//关闭session

MyBatisUtil.closeSession();

}

}

@Test

public void testfindDeptByName() {

SqlSession session = null;

try {

//获取session

session = MyBatisUtil.getSession();

//获取mapper接口的代理对象

DeptMapper deptMapper = session.getMapper(DeptMapper.class);

//准备数据

Map map = new HashMap();

map.put("deptName", "%研%");

//调用代理对象的方法,由于要返回List类型,动态代理对象将调用sqlSession.selectList()方法

List<Dept> depts = deptMapper.findDeptByName(map);

for (Dept dept : depts) {

System.out.println(dept);

}

} catch (Exception e) {

e.printStackTrace();

} finally {

//关闭session

MyBatisUtil.closeSession();

}

}

}

parameterType传递pojo包装对象

开发中通过pojo传递查询条件,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。

定义包装对象将查询条件(pojo)以类组合的方式包装起来:

public class QueryVo {

private User user;

//引用自定义用户扩展类

private UserCustom userCustom;

省略getter、setter、toString方法

}

/**

 * 创建自定义用户扩展类

 */

public class UserCustom extends User{

}

mapper.xml映射文件:

说明:mybatis底层通过ognl从pojo中获取属性值:#{user.username},user即是传入的包装对象的属性。queryVo是别名,即上边定义的包装对象类型。

动态SQL操作判断条件不要用ognl表达式

if语句

<!-- 动态if条件 -->

<select id="findDeptList" parameterType="Dept"  resultMap="deptResultMap">

select * from dept where 1=1

<if test="deptId!=null and deptId!=''">

and dept_id=#{deptId}

</if>

<if test="deptName!=null and deptName!=''">

and dept_name=#{deptName}

</if>

<if test="deptAddress!=null and deptAddress!=''">

and dept_address=#{deptAddress}

</if>

</select>

where(if)语句

<!-- 动态where条件 ,一般也需要与if结合使用,与纯if比较,它自动处理第一个and,省略了where 1=1 -->

<select id="findDeptList" parameterType="Dept"  resultMap="deptResultMap">

select * from dept

<where>

<if test="null!=deptId and ''!=deptId">

and dept_id=#{deptId}

</if>

<if test=" null!=deptName! and ''!=deptName">

and dept_name=#{deptName}

</if>

<if test=" null!=deptAddress and ''!=deptAddress">

and dept_address=#{deptAddress}

</if>

</where>

</select>

choose(when,otherwise)语句

<select id="selectListUseChoose" parameterType="Dept" resultMap="deptResultMap">

select * from dept where 1=1

<choose>

<when test="deptId!=null">and dept_id=#{deptId}</when>

<when test="deptName!=null">and dept_name=#{deptName}</when>

<when test="deptAddress!=null">and dept_address=#{deptAddress}</when>

<otherwise>and !1 = 1</otherwise>

</choose>

</select>

set语句

<!--动态set语句可以用来更新数据 -->

<update id="updateUseSet" parameterType="Dept">

update dept

<set>

<if test="deptName!=null">dept_name=#{deptName},</if>

<if test="deptAddress!=null">dept_address=#{deptAddress},</if>

</set>

where dept_id=#{deptId}

</update>

foreach语句

向sql传递数组或List,mybatis使用foreach解析

<!--根据多个部门ID查询部门列表,resultMap的值是指集合里元素的类型,parameterType不用指定 -->

<select id="findDeptListByIds"  resultMap="deptResultMap">

select * from dept where dept_id in

<!—

    collection的值: 若参数为数组,则值为"array",

  若参数为集合,则值为"list",

  若参数为数组/集合但封装在pojo属性,则值为pojo属性名。

    item的值:item代表数组或集合中的元素,名称随意。

    占位符的值:若数组/集合中元素为简单类型,则占位符填item值;若元素为pojo类型,则占位符填item值.pojo属性名

-->

<foreach collection="array" item="deptId" open="(" separator="," close=")">

#{deptId}

</foreach>

</select>

include语句(引用SQL片段)

注意:如果引用其它mapper.xml的sql片段,则在引用时需要加上namespace,如下:

<include refid="namespace.sql片段”/>

例一:使用include语句动态插入表的字段及对应的值

例二:使用include语句动态追加where判断

<!-- 定义SQL片段 -->

<sql id="key">

<!--suffixOverrides="," 可以忽略最后“,”号 -->

<trim suffixOverrides=",">

<if test="deptName!=null">

dept_name,

</if>

<if test="deptAddress!=null">

dept_address,

</if>

</trim>

</sql>

<sql id="value">

<trim suffixOverrides="," >

<if test="deptName!=null">

#{deptName},

</if>

<if test="deptAddress!=null">

#{deptAddress},

</if>

</trim>

</sql>

<!-- 引用SQL片段 -->

<insert id="insertUseInclude" parameterType="Dept">

insert into dept(

<include refid="key" />

) values(

<include refid="value"/>

)

</insert>

<!-- 定义SQL片段 -->

<sql id="query_user_where">

<if test="id!=null and id!=''">

and id=#{id}

</if>

<if test="username!=null and username!=''">

and username like '%${username}%'

</if>

</sql>

<!-- 引用SQL片段 -->

<select id="findUserList" parameterType="user" resultType="user">

select * from user

<where>

<include refid="query_user_where" />

</where>

</select>

关联查询

创建数据库及表

drop database if exists mybatis;

create database mybatis CHARACTER SET UTF8;

use mybatis;

create table dept(

    dept_id int primary key auto_increment,

    dept_name varchar(50),

    dept_address varchar(50)

);

create table emp(

    emp_id varchar(18) primary key,

    emp_name varchar(50),

    emp_sex char(1),

    dept_id int

);

insert into dept(dept_name,dept_address) values('研发部一部','广州');

insert into dept(dept_name,dept_address) values('研发部二部','广州');

insert into dept(dept_name,dept_address) values('研发部三部','深圳');

insert into emp(emp_id,emp_name,emp_sex,dept_id) values('44152199507052110','张大',"男","1");

insert into emp(emp_id,emp_name,emp_sex,dept_id) values('44152199507052111','张一',"女","1");

insert into emp(emp_id,emp_name,emp_sex,dept_id) values('44152199507052112','张二',"男","1");

select * from dept;

select * from emp;

基于association查询(用于多对一或一对一)

创建实体类

部门实体类:

员工实体类:

public class Dept implements Serializable{

private String deptAddress;

private String deptName;

private Integer deptId;

省略setter和getter方法

}

public class Emp implements Serializable{

private String empId;

private String empName;

private String empSex;

private Dept dept;

省略settergetter方法

}

配置DeptMapper.xmlEmpMapper.xml

(重点加入级联的查询语句),并映射文件信息到mybatis-config.xml中:

DeptMapper.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="cn.itcast.entity.DeptMapper">

<!-- 表字段和对应实体属性命名一致时可以不配置 -->

<resultMap id="deptResultMap" type="Dept">

<id property="deptId" column="dept_id" />

<result property="deptName" column="dept_name" />

<result property="deptAddress" column="dept_address" />

</resultMap>

</mapper>

EmpMapper.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="cn.itcast.entity.EmpMapper">

<!-- 表字段和对应实体属性命名一致时可以不配置 -->

<resultMap id="empResultMap" type="Emp">

<id property="empId" column="emp_id" />

<result property="empName" column="emp_name" />

<result property="empSex" column="emp_sex" />

<!-- association配置多对一关联(column要不要?) -->

<association property="dept" column="dept_id" javaType="Dept" resultMap="cn.itcast.entity.DeptMapper.deptResultMap" />

</resultMap>

<!--根据部门名称查询员工(包括员工所在部门)信息 -->

<select id="selectEmpDeptList" parameterType="Emp" resultMap="empResultMap">

<!-- 访问emp.dept.deptName, 前面不用写emp, 直接写 dept.deptName-->

select e.*,d.* from emp e inner join dept d on

e.dept_id=d.dept_id where d.dept_name like #{dept.deptName}

</select>

</mapper>

配置文件myBatis-config.xml

<!-- 通过别名简化对类的使用 -->

<typeAliases>

<typeAlias type="cn.itcast.entity.Dept" alias="Dept" />

<typeAlias type="cn.itcast.entity.Emp" alias="Emp" />

</typeAliases>

 …….

<!--导入SQL映射文件 -->

<mappers>

<mapper resource="cn/itcast/entity/DeptMapper.xml" />

<mapper resource="cn/itcast/entity/EmpMapper.xml" />

</mappers>

编写EmpDaoImpl.java实现查询

public class EmpDaoImpl {

SqlSession session;

public List<Emp> selectEmpDeptList(Emp emp){

List<Emp> emps=null;

try{

session=MyBatisUtil.getSession();

emps=session.selectList("cn.itcast.entity.EmpMapper.selectEmpDeptList",emp);

//session.commit();

}catch (Exception e) {

e.printStackTrace();

//session.rollback();

}finally{

MyBatisUtil.closeSession();

}

return emps;

}

}

编写测试类

EmplTest.java:

@Test

public void selectEmpDeptList() {

Emp emp=new Emp();

Dept dept=new Dept();

dept.setDeptName("%研%");

emp.setDept(dept);

List<Emp> emps=empDaoImpl.selectEmpDeptList(emp);

for(Emp emp1:emps){

System.out.println("emp="+emp1);

//System.out.println("dept="+emp1.getDept());

}

}

基于collection查询(用于一对多或多对多)

编写实体类

Dept.java

Emp.java

public class Dept implements Serializable{

private String deptAddress;

private String deptName;

private Integer deptId;

private List<Emp> emps;

省略set和get方法

}

public class Emp implements Serializable{

private String empId;

private String empName;

private String empSex;

省略set和get方法

}

配置DeptMapper.xml和EmpMapper.xml

DeptMapper.xml文件,配置resultMap(在association查询基础上增加collection配置和查询SQL语句):

<mapper namespace="cn.itcast.entity.DeptMapper">

<!-- 表字段和对应实体属性命名一致时可以不配置 -->

<resultMap id="deptResultMap" type="Dept">

<id property="deptId" column="dept_id" />

<result property="deptName" column="dept_name" />

<result property="deptAddress" column="dept_address" />

<!-- collection中resultMap引用的是其它文件的map 需要命名空间+id,如本例column要不要? -->

<collection property="emps"  ofType="Emp" resultMap="cn.itcast.entity.EmpMapper.empResultMap"/>

</resultMap>

<select id="selectDeptEmpList" parameterType="Dept" resultMap="deptResultMap">

select d.*, e.* from dept d inner join emp e on d.dept_id=e.dept_id where d.dept_name like #{deptName}

</select>

</mapper>

EmpMapper.xml

<mapper namespace="cn.itcast.entity.EmpMapper">

<!-- 表字段和对应实体属性命名一致时可以不配置 -->

<resultMap id="empResultMap" type="Emp">

<id property="empId" column="emp_id" />

<result property="empName" column="emp_name" />

<result property="empSex" column="emp_sex" />

</resultMap>

</mapper>

配置文件myBatis-config.xml

不用配置多对一关联?

编写DeptDaoImpl.java实现查询

public class DeptDaoImpl {

//同时查询部门及部门员工信息

public List<Dept> selectDeptEmpList(Dept dept){

SqlSession session=null;

List<Dept> deps=null;

try{

session=MyBatisUtil.getSession();

deps=session.selectList("cn.itcast.entity.DeptMapper.selectDeptEmpList",dept);

session.commit();

}catch (Exception e) {

e.printStackTrace();

session.rollback();

}finally{

MyBatisUtil.closeSession();

}

return deps;

}

}

编写测试类

DeptTest.java:

@Test

public void selectDeptEmpList() {

Dept paramDept = new Dept();

paramDept.setDeptName("%研%");

List<Dept> depts = deptDaoImpl.selectDeptEmpList(paramDept);

for (Dept dept : depts) {

System.out.println("dept:" + dept);

}

}

混合查询

<!-- 查询用户及购买的商品 -->

<!-- 用户信息 -->

<resultMap type="cn.itcast.mybatis.po.User" id="UserAndItemsResultMap">

<id column="user_id" property="id"/>

<result column="username" property="username"/>

<result column="sex" property="sex"/>

<result column="address" property="address"/>

<!-- 订单信息:一个用户对应多个订单 -->

 <collection property="ordersList" ofType="cn.itcast.mybatis.po.Orders">

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

<!-- 订单明细:一个订单包括多个明细 -->

   <collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">

<id column="orderdetail_id" property="id"/>

<result column="items_id" property="itemsId"/>

<result column="items_num" property="itemsNum"/>

<result column="orders_id" property="ordersId"/>

<!-- 商品信息:一个订单明细对应一个商品 -->

     <association property="items" javaType="cn.itcast.mybatis.po.Items">

     <id column="items_id" property="id"/>

     <result column="items_name" property="name"/>

     <result column="items_detail" property="detail"/>

     <result column="items_price" property="price"/>

     </association>

   </collection>

 </collection>

</resultMap>

<!-- 查询用户及购买的商品信息,使用resultmap -->

<select id="findUserAndItemsResultMap" resultMap="UserAndItemsResultMap">

SELECT

  USER.username,

  USER.sex,

  USER.address,

  orders.number,

  orders.createtime,

  orders.note,

  orderdetail.items_id,

  orderdetail.items_num,

  orderdetail.orders_id,

  items.name items_name,

  items.detail items_detail,

  items.price items_price

FROM

  USER,

  orders,

  orderdetail,

  items

WHERE user.id=orders.user_id AND orders.id=orderdetail.orders_id AND orderdetail.items_id = items.id

</select>

一对多双向关联查询示例

编写实体类:Dept.java/Emp.java

Dept.java

Emp.java

public class Dept implements Serializable{

private String deptAddress;

private String deptName;

private Integer deptId;

private List<Emp> emps;

省略set和get方法

}

public class Emp implements Serializable{

private String empId;

private String empName;

private String empSex;

private Dept dept;

省略set和get方法

}

编写DeptMapper.xml/EmpMapper.xml文件

DeptMapper.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="cn.itcast.entity.DeptMapper">

<!-- 表字段和对应实体属性命名一致时可以不配置 -->

<resultMap id="deptResultMap" type="Dept">

<id property="deptId" column="dept_id" />

<result property="deptName" column="dept_name" />

<result property="deptAddress" column="dept_address" />

</resultMap>

<!-- 一对多时,“多”的关联属性可独立配置resultMap,采用extends继承基本属性的resultMap -->

<resultMap id="deptExtResultMap" type="Dept" extends="deptResultMap">

<!-- collection中resultMap引用的是其它文件的map 需要命名空间+id,如本例 -->

<collection property="emps" ofType="Emp" resultMap="cn.itcast.entity.EmpMapper.empResultMap" />

</resultMap>

<!--用于部门和员工关联查询,返回部门信息(包含部门员工信息)列表,采用extends继承基本属性的resultMap -->

<select id="selectDeptEmpList" parameterType="Dept" resultMap="deptExtResultMap">

select d.*, e.* from dept d inner join emp e on d.dept_id=e.dept_id

where d.dept_name like #{deptName}

</select>

</mapper>

EmpMapper.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="cn.itcast.entity.EmpMapper">

<!-- 表字段和对应实体属性命名一致时可以不配置 -->

<resultMap id="empResultMap" type="Emp">

<id property="empId" column="emp_id" />

<result property="empName" column="emp_name" />

<result property="empSex" column="emp_sex" />

<!--注意association元素的resultMap的值为没有配置“多”的属性映射的deptResultMap,如下 -->

<association property="dept" column="dept_id" resultMap="cn.itcast.entity.DeptMapper.deptResultMap"/>

</resultMap>

<!-- 用于员工和部门关联查询,返回员工信息(包含部门信息)列表 -->

<select id="selectEmpDeptList" parameterType="Emp" resultMap="empResultMap">

select e.*,d.* from emp e inner join dept d on

e.dept_id=d.dept_id where d.dept_name like #{dept.deptName}

</select>

</mapper>

编写数据操作类:DeptDaoImpl.java/EmpDaoImpl.java

DeptDaoImpl.java,查询部门员工信息,返回类型为List<Dept>,关键代码:

public List<Dept> selectDeptEmpList(Dept dept){

SqlSession session=null;

List<Dept> deps=null;

try{

session=MyBatisUtil.getSession();

deps=session.selectList("cn.itcast.entity.DeptMapper.selectDeptEmpList",dept);

session.commit();

}catch (Exception e) {

e.printStackTrace();

session.rollback();

}finally{

MyBatisUtil.closeSession();

}

return deps;

}

EmpDaoImpl.java

查询员工及其所在部门信息,返回类型为List< Emp >,关键代码

public List<Emp> selectEmpDeptList(Emp emp){

SqlSession session=null;

List<Emp> emps=null;

try{

session=MyBatisUtil.getSession();

emps=session.selectList("cn.itcast.entity.EmpMapper.selectEmpDeptList",emp);

session.commit();

}catch (Exception e) {

e.printStackTrace();

session.rollback();

}finally{

MyBatisUtil.closeSession();

}

return emps;

}

编写测试类:DeptTest.java/EmpTest.java

DeptTest.java关键代码

//测试部门和员工的关联查询,并遍历装载部门类型的结果集

@Test

public void selectDeptEmpList() {

Dept paramDept=new Dept();

paramDept.setDeptName("%研%");

List<Dept> depts=deptDaoImpl.selectDeptEmpList(paramDept);

for(Dept dept:depts){

System.out.println("dept:"+dept);

}

}

EmpTest.java关键代码

//测试员工和部门的关联查询,并遍历装载员工类型的结果集

@Test

public void selectEmpDeptList() {

Emp emp=new Emp();

Dept dept=new Dept();

dept.setDeptName("%研%");

emp.setDept(dept);

List<Emp> emps=empDaoImpl.selectEmpDeptList(emp);

for(Emp emp1:emps){

System.out.println("emp="+emp1);

System.out.println("dept="+emp1.getDept());

}

}

什么是延迟加载

resultMap中的association和collection标签具有延迟加载的功能。

延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息。使用关联信息时再去加载关联信息。

设置延迟加载

需要在SqlMapConfig.xml文件中,在<settings>标签中设置延迟加载。

<settings>

<!-- 开启延迟加载 -->

<!-- lazyLoadingEnabled:延迟加载启动,默认是false -->

<setting name="lazyLoadingEnabled" value="true"/>

<!-- aggressiveLazyLoading:积极的懒加载,false的话按需加载,默认是true -->

<setting name="aggressiveLazyLoading" value="false"/>

<!-- 开启二级缓存,默认是false -->

<setting name="cacheEnabled" value="true"/>

</settings>

Mybatis和Hibernate一样,也使用缓存。缓存分为一级缓存和二级缓存。

一级缓存的作用域是一个SqlSession。Mybatis默认开启一级缓存。

二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享。Mybatis需要手动设置启动二级缓存。

原理:在同一个作用域下(SqlSession或namespace),执行相同的查询SQL,第一次会去查询数据库,并写到缓存中;第二次直接从缓存中取。当执行SQL时两次查询中间发生了增删改操作,则缓存清空,后续查询数据库时重新写到缓存中。

Mybatis的内部缓存使用一个HashMap,key为hashcode+statementId+sql语句。Value为查询出来的结果集映射成的java对象。

准备工作

数据库及表信息

drop database if exists mybatis;

create database mybatis CHARACTER SET UTF8;

use mybatis;

create table dept(

    dept_id int primary key auto_increment,

    dept_name varchar(50),

    dept_address varchar(50)

);

insert into dept(dept_name,dept_address) values('研发部一部','广州');

insert into dept(dept_name,dept_address) values('研发部二部','广州');

insert into dept(dept_name,dept_address) values('研发部三部','深圳');

select * from dept;

实体类:

public class Dept implements Serializable {

private Integer deptId;

private String deptName;

private String deptAddress;

//省略set/get方法

工具类,数据库操作类,测试类(略)

一级缓存测试

关健方法代码:

//用来测试一级缓存

public void selectDept(Integer deptId){

SqlSession session=null;

try {

session =MyBatisUtil.getSession();

//命名空间+id,如cn.itcast.entity.DeptMapper.selectDept

Dept dept1=session.selectOne("cn.itcast.entity.DeptMapper.selectDept", deptId);

System.out.println("dept1:"+dept1);

Dept dept2=session.selectOne("cn.itcast.entity.DeptMapper.selectDept", deptId);

System.out.println("dept1:"+dept1);

} catch (Exception e) {

e.printStackTrace();

}finally{

try {

MyBatisUtil.closeSession();

} catch (Exception e) {

e.printStackTrace();

}

}

}

测试用例

@Test //测试一级缓存

public void testCache() {

deptDaoImpl.selectDept(1);

}

测试结果

DEBUG 2014-11-08 16:32:45,484 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.

DEBUG 2014-11-08 16:32:45,578 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection

DEBUG 2014-11-08 16:32:45,859 org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 25374911.

DEBUG 2014-11-08 16:32:45,859 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ooo Using Connection [com.mysql.jdbc.JDBC4Connection@18330bf]

DEBUG 2014-11-08 16:32:45,859 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select * from dept where dept_id=?

DEBUG 2014-11-08 16:32:45,906 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)

dept1:Dept [deptId=1, deptName=研发部一部, deptAddress=广州]

dept1:Dept [deptId=1, deptName=研发部一部, deptAddress=广州]

DEBUG 2014-11-08 16:32:45,937 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@18330bf]

DEBUG 2014-11-08 16:32:45,937 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@18330bf]

DEBUG 2014-11-08 16:32:45,937 org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 25374911 to pool.

结果分析:

发现只执行一次SQL语句,第二次查询时直接从缓存中获得,默认开启一级缓存。

二级缓存(默认配置)测试

操作类关键方法代码

//用来测试二级缓存

public Dept testCache2(Integer deptId){

SqlSession session=null;

Dept dept=null;

try {

session =MyBatisUtil.getSession();

//命名空间+id,如cn.itcast.entity.DeptMapper.selectDept

dept=session.selectOne("cn.itcast.entity.DeptMapper.selectDept", deptId);

} catch (Exception e) {

e.printStackTrace();

}finally{

try {

MyBatisUtil.closeSession();

} catch (Exception e) {

e.printStackTrace();

}

}

return dept;

}

测试用例代码

@Test //用来测试二级缓存

public void testCache2() {

Dept dept = deptDaoImpl.testCache2(1);

System.out.println("dept:" + dept);

Dept dept1 = deptDaoImpl.testCache2(1);

System.out.println("dept1"+dept1);

}

测试结果

DEBUG 2014-11-08 16:40:52,717 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ooo Using Connection [com.mysql.jdbc.JDBC4Connection@18330bf]

DEBUG 2014-11-08 16:40:52,717 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select * from dept where dept_id=?

DEBUG 2014-11-08 16:40:52,764 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@18330bf]

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@18330bf]

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 25374911 to pool.

dept:Dept [deptId=1, deptName=研发部一部, deptAddress=广州]

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.datasource.pooled.PooledDataSource: Checked out connection 25374911 from pool.

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@18330bf]

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ooo Using Connection [com.mysql.jdbc.JDBC4Connection@18330bf]

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select * from dept where dept_id=?

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@18330bf]

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@18330bf]

DEBUG 2014-11-08 16:40:52,811 org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 25374911 to pool.

dept1Dept [deptId=1, deptName=研发部一部, deptAddress=广州]

测试结果分析

发现执行了两次sql语句(红色部分),默认为不启用二级缓存。

二级缓存配置

使用二级缓存机制:需要开启全局缓存,文件级缓存 ,语句级缓存,才能使用二级缓存。默认情况下文件级缓存没有开启。

全局缓存配置

在mybatis的主配置文件中进行配置:

<?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>

<settings>

<!-- 默认有启用全局缓存的,禁用可以把value设为false,如果这里设为false,Mapper.xml或SQL语句级的缓存配置不再起作用 -->

<setting name="cacheEnabled" value="true"/>

</settings>

<!—省略其它配置信息 -->

</configuration>

Mapper文件级缓存配置

<?xml version="1.0" encoding="UTF-8" ?> 

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.itcast.entity.DeptMapper">

<cache/>

<!—省略其它配置信息-->

</mapper>

SQL语句级缓存配置

在相关的Mapper.xml文件中配置SQL查询,关键代码如下(示例):

<!--  useCache默认值为true,设为false时缓存不起作用 -->

<select id="selectOne" parameterType="int" resultMap="deptResultMap" useCache="true" >

select * from dept where dept_id=#{id}

</select>

实现序列化

由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象(POJO)执行序列化。

如果该类存在父类,那么父类也要实现序列化。

刷新二级缓存:statement中添加属性flushCache="true"

<select id="selectOne" parameterType="int" resultMap="deptResultMap" useCache="true" flushCache="true" >

select * from dept where dept_id=#{id}

</select>

缓存分析

修改DeptMapper.xml文件,增加缓存的设置,如下

<!--

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="false"/>

创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。可用的收回策略有以下几种, 默认的是 LRU:

1. LRU – 最近最少使用的:移除最长时间不被使用的对象。

2. FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

3. SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

4. WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

flushInterval:刷新间隔时间,可以被设置为任意的正整数,单位毫秒。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

size:内存资源数目,可以被设置为任意正整数。默认值是1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。

 -->

<cache eviction="LRU" size="2" readOnly="false" />

测试代码

@Test

public void testSelectDept() {

 deptDaoImpl.selectDept(1);

 deptDaoImpl.selectDept(2);

 deptDaoImpl.selectDept(2);

 deptDaoImpl.selectDept(1);

 deptDaoImpl.selectDept(3);

deptDaoImpl.selectDept(1);

 deptDaoImpl.selectDept(2);

}

XML 中的特殊字符处理

    如果 MyBatis 使用 XML 配置,那不可避免地会遇到一些对 XML 来说是特殊的字符。如小于号 “<”,因此要进行转义。主要有两个方式:

使用转义实体

下面是五个在 XML 文档中预定义好的转义实体:

<     < 小于号

>     > 大于号

&   &

'   ' 单引号

"   " 双引号

小于等于“<=”,其转义为:<=

大小等于“>=”,转义为:>=

使用 CDATA 部件

  CDATA 部件以"<![CDATA[" 标记开始,以"]]>"标记结束。在"<![CDATA["和"]]>"之间 的特殊字符的意义都不起作用,而转变为普通字符串内容。

   在 MyBatis 的 XML 映射语句配置文件中,如果 SQL 语句有特殊字符,使用CDTA 部件括起来,如:

<select id= "selectBlog_use_collection"

resultMap= "blogResult" >

<![CDATA[ SELECT id , title, author_id as authored FROM BLOG WHERE ID > 0 and ID < 10 ]]> </select>

常用批量操作

准备数据

drop database if exists mybatis;

create database mybatis CHARACTER SET UTF8;

use mybatis;

create table dept(

    dept_id int primary key auto_increment,

    dept_name varchar(50),

    dept_address varchar(50)

);

insert into dept(dept_name,dept_address) values('研发部一部','广州');

insert into dept(dept_name,dept_address) values('研发部二部','广州');

insert into dept(dept_name,dept_address) values('研发部三部','深圳');

--insert into dept( dept_name, dept_address ) values ('研发部4部','广州'),('研发部5部','广州'),('研发部6部','广州')

select * from dept;

批量新增部门

映射文件定义SQL

<sql id="key">

<!--suffixOverrides="," 可以忽略最后“,”号 -->

<trim suffixOverrides=",">

dept_name,

dept_address,

</trim>

</sql>

<insert id="insertDeptList">

insert into dept(

<include refid="key" />

) values

<foreach collection="list" item="item" separator=",">

(#{item.deptName},#{item.deptAddress})

</foreach>

</insert>

编写批量添加部门方法(数据操作类定义批量添加部门的方法)

public int saveDeptList(List<Dept> depts) {

int i = 0;

SqlSession session = null;

try {

session = MyBatisUtil.getSession();

i = session.insert("cn.itcast.entity.DeptMapper.insertDeptList", depts);

session.commit();

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

session.rollback();

} finally {

MyBatisUtil.closeSession();

}

return i;

}

编写测试代码

@Test

public void testInsertDeptList() {

List<Dept> depts = new ArrayList<Dept>();

for(int i=0;i<5;i++){

Dept dept = new Dept();

dept.setDeptName("deptname"+i);

dept.setDeptAddress("address"+i);

depts.add(dept);

}

int i=deptDao.saveDeptList(depts);

System.out.println("受影响的行数:"+i);

}

批量删除部门

映射文件定义SQL

<delete id="deleteDeptList">

delete from dept where dept_id in

<foreach collection="list" item="item" open="(" close=")"

separator=",">

#{item}

</foreach>

</delete>

编写批量删除部门的方法

public int deleteDeptList(List<Integer> deptIds) {

int i = 0;

SqlSession session = null;

try {

session = MyBatisUtil.getSession();

i = session.delete("cn.itcast.entity.DeptMapper.deleteDeptList", deptIds);

session.commit();

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

session.rollback();

} finally {

MyBatisUtil.closeSession();

}

return i;

}

编写测试代码

@Test

public void testDeleteDeptList() {

List<Integer> deptIds = new ArrayList<Integer>();

for(int i=4;i<7;i++){

deptIds.add(i);

}

int i=deptDao.deleteDeptList(deptIds);

System.out.println("受影响的行数:"+i);

}

批量修改员工信息

修改mybatis-config.xml文件

支持上有点麻烦,需要修改mybatis-config.xml文件相关数据库连接的信息(主要红色部分),以支持批量更新

<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true" />

road data infi

配置批量更新的sql

<update id="updateDeptList">

<!-- 多个update语句,用;号分隔开,如果是oracle数据库,一般需要加“begin”前缀,后缀“end;" -->

<foreach collection="list" item="dept" separator=";">

update dept

<set>

<if test="dept.deptName!=null">dept_name=#{dept.deptName},</if>

<if test="dept.deptAddress!=null">dept_address=#{dept.deptAddress},</if>

</set>

where dept_id=#{dept.deptId}

</foreach>

</update>

编写批量更新部门的方法

public int updateDeptList(List<Dept> depts) {

int i = 0;

SqlSession session = null;

try {

session = MyBatisUtil.getSession();

i = session.update("cn.itcast.entity.DeptMapper.updateDeptList", depts);

session.commit();

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

session.rollback();

} finally {

MyBatisUtil.closeSession();

}

return i;

}

编写测试代码

@Test

public void testUpdateDeptList() {

List<Dept> depts = new ArrayList<Dept>();

for(int i=1;i<4;i++){

Dept dept = new Dept();

dept.setDeptId(i);

dept.setDeptName("deptName"+i);

dept.setDeptAddress("deptAddress" + i);

depts.add(dept);

}

int i=deptDao.updateDeptList(depts);

System.out.println("受影响的行数:"+i);

}

Mybatis中接口和对应的mapper文件位置配置深入剖析

首先要说明的问题是,Mybatis中接口和对应的mapper文件不一定要放在同一个包下,放在一起的目的是为了Mybatis进行自动扫描,并且要注意此时java接口的名称和mapper文件的名称要相同,否则会报异常,由于此时Mybatis会自动解析对应的接口和相应的配置文件,所以就不需要配置mapper文件的位置了。

接口和文件在同一个包中

默认maven构建

如果在工程中使用了maven构建工具,那么就会出现一个问题:我们知道在典型的maven工程中,目录结构有:src/main/javasrc/main/resources,前者是用来存放java源代码的,后者则是存放一些资源文件,比如配置文件等,在默认的情况下maven打包的时候,对于src/main/java目录只打包源代码,而不会打包其他文件。所以此时如果把对应的mapper文件放到src/main/java目录下时,不会打包到最终的jar文件夹中,也不会输出到target文件夹中,由于在进行单元测试的时候执行的是/target目录下/test-classes下的代码,所以在测试的时候也不会成功。

为了实现在maven默认环境下打包时,Mybatis的接口和mapper文件在同一包中,可以通过将接口文件放在src/main/java某个包中,将映射文件放在src/main/resources同样的包,这是一种约定优于配置的方式,这样在maven打包的时候就会将src/main/javasrc/main/resources相同包下的文件合并到同一包中。

在默认maven打包的环境下,不要将接口文件和mapper文件全部放到src/main/java,这样也不会把mapper文件打包进去

更改maven构建配置

如果不想将接口和mapper文件分别放到src/main/javasrc/main/resources中,而是全部放到src/main/java,那么在构建的时候需要指定maven打包需要包括xml文件,具体配置如下:

<build>

    <resources>

        <resource>

            <directory>src/main/java</directory>

            <includes>

                <include>**/*.xml</include>

            </includes>

            <filtering>false</filtering>

        </resource>

    </resources>

</build>

这样在打包的时候也会将mapper文件打包到/target文件夹中。

接口和文件不在同一个包下

如果接口和mapper文件不在同一个包下,就不能进行自动扫描解析了,需要对接口和文件分别进行配置。

XML配置方式

使用Mybatis的配置文件如下:

<?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>

  <mappers>

    <!-- 扫描路径下的mapper映射文件 -->

    <mapper resource="mappers/UserMapper.xml"/>

    <!-- 扫描包下的接口文件 -->

    <package name="edu.zju.bme.data.manage.mapper" />

  </mappers>

</configuration>

使用Spring的配置文件如下:

<beans xmlns="http://www.springframework.org/schema/beans"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"

  xsi:schemaLocation="

  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

  http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

  <!-- 配置接口存储的包,用来扫描mapper接口 -->

  <mybatis:scan base-package="edu.zju.bme.data.manage.mapper" />

  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

    <!-- 配置mapper文件位置,扫描映射文件,可以使用Ant风格的路径格式 -->

    <property name="mapperLocations" value="classpath*:mappers/**/*.xml" />

    // ...

  </bean>

</beans>

Java 配置方式

不使用Spring

Configuration configuration = new Configuration();

configuration.setMapUnderscoreToCamelCase(true);

configuration.setLazyLoadingEnabled(true);

configuration.setCacheEnabled(false);

// 扫描该包下的接口和mapper文件

configuration.addMappers("edu.zju.bme.data.manage.mapper");

使用这种方法,只能将接口和mapper文件放到同一个包下,并且同名,相比其他方法具有局限性。

使用Spring

// 配置类

@Configuration(value = "manageConfig")

@Import(value = {DataSourceConfig.class})

// 扫描接口类,这个配置只能扫描该包下的接口,不能扫描mapper文件

@MapperScan("edu.zju.bme.data.manage.mapper")

public class ManageConfig {

    @Autowired

    private DataSourceConfig dataSourceConfig;

    @Bean(name = "manageSessionBean")

    public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException{

        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

        factoryBean.setDataSource(dataSourceConfig.manageDataSource());

        Configuration configuration = new Configuration();

        // 扫描对应的mapper文件

        factoryBean.setMapperLocations(new Resource[]{new ClassPathResource("UserMapper.xml")});

        factoryBean.setConfiguration(configuration);

        return factoryBean;

    }

}

上面总结了4种配置方式,包括使用Spring以及不使用Spring的环境下,但是要注意以上的配置方式并不是唯一的,还有其他方法,如配置org.mybatis.spring.mapper.MapperScannerConfigurer,这里只是选择了相对来说较为简单的方式。

    • TypeHandler 映射JSON类型为List

实体类分别在所需映射的字段和实体类上添加注解(实测不搞这部也OK)

package com.tongwx.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
//@TableName(value = "user", autoResultMap = true)
public class User {
    private Integer id;
    private String name;
    private Integer age;
    private String idCard;
    //@TableField(typeHandler = CertificateListTypeHandler.class)
    private List<Certificate> certificates;
}

提供一个字符串与对象集合互转的处理器ListTypeHandler

package com.tongwx.demo.common.typeHandler;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

//@MappedJdbcTypes(JdbcType.VARBINARY)
//@MappedTypes({List.class})
public abstract class ListTypeHandler<T> extends BaseTypeHandler<List<T>> {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @SneakyThrows
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException {
        String content = CollectionUtils.isEmpty(parameter) ? null : OBJECT_MAPPER.writeValueAsString(parameter);
        ps.setString(i, content);
    }

    @Override
    public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return this.getListByJsonArrayString(rs.getString(columnName));
    }

    @Override
    public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return this.getListByJsonArrayString(rs.getString(columnIndex));
    }

    @Override
    public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return this.getListByJsonArrayString(cs.getString(columnIndex));
    }


    @SneakyThrows
    private List<T> getListByJsonArrayString(String content) {
        return StringUtils.isEmpty(content) ? new ArrayList<>() : OBJECT_MAPPER.readValue(content, this.specificType());
    }

    /**
     * 具体类型,由子类提供
     * @return 具体类型
     */
    protected abstract TypeReference<List<T>> specificType();

}

由具体的子类提供List集合泛型类型

package com.tongwx.demo.common.typeHandler;

import com.fasterxml.jackson.core.type.TypeReference;
import com.tongwx.demo.entity.Certificate;

import java.util.List;

public class CertificateListTypeHandler extends ListTypeHandler<Certificate> {

    @Override
    protected TypeReference<List<Certificate>> specificType() {
        return new TypeReference<List<Certificate>>() {
        };
    }
}

Mapper文件相关地方写上typeHandler

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tongwx.demo.dao.UserMapper">

   <resultMap id="userMap" type="com.tongwx.demo.entity.User">
      <id property="id" column="id" />
      <result property="idCard" column="id_card" />
      <result property="name" column="name" />
      <result property="age" column="age" />
      <result property="certificates" column="certificates"
            typeHandler="com.tongwx.demo.common.typeHandler.CertificateListTypeHandler"
            />
   </resultMap>

    <insert id="save" parameterType="com.tongwx.demo.entity.User">
      insert into user(id_card, name, age, certificates)
       values(#{idCard}, #{name}, #{age}, #{certificates,typeHandler=com.tongwx.demo.common.typeHandler.CertificateListTypeHandler})
   </insert>

</mapper>

    1. MyBatis

基于MyBatis之DAO接口的编程步骤

1)导包

spring-webmvc : 3.2.8.RELEASE

mybatis : 3.2.8

mybatis-spring : 1.2.3

spring-jdbc : 3.2.8.RELEASE

ojdbc14 : 10.2.0.4.0

commons-dbcp : 1.4

junit : 4.12

2)添加MyBatis配置文件mybatis-config.xml

注意:不需配置运行环境(运行环境将在Spring配置文件中配置)

<?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>

<!-- ★★★★★★★★★★引用映射文件★★★★★★★★★★ -->

<mappers>

</mappers>

</configuration>

3)创建数据库库及表创建实体类。

注意:如果要解决属性名与表的字段名不一致的情况,建议用别名。

4)编写映射文件,修改mybatis-config.xml指定映射文件位置。

5)进行简单测试

(除了导入spring相关jar包外,以上步骤具体参见MyBatis编程步骤)

6)配置applicationContext.xml

beans根元素

<!-- 配置数据源,记得去掉myBatis-config.xml的数据源相关配置 -->

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

<property name="driverClass" value="com.mysql.jdbc.Driver" />

<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8" />

<property name="user" value="root" />

<property name="password" value="root" />

</bean>

<!-- 配置session工厂 -->

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<!-- 指定数据源,引用上面的数据源配置 -->

<property name="dataSource" ref="dataSource" />

<!-- 指定MyBatis主配置文件位置 --><!-- 传智 -->

<property name="configLocation" value="classpath:myBatis-config.xml" />

<!-- 指定映射文件位置 --><!-- 达内 -->

<!-- <property name="mapperLocations" value="classpath:com/tongwx/entity/*.xml" /> -->

</bean>

<!-- 配置事务管理器,管理数据源事务处理-->

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<!-- 指定要管理的数据源,引用上面的数据源配置 -->

<property name="dataSource" ref="dataSource" />

</bean>

<!-- 配置SessionTemplate,已封装了SqlSession的数据操作-->

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">

<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>

</bean>

beans根元素

SqISessionFactoryBean的常用property:

dataSource:用于指定连接数据库的数据源(必要属性)

mapperLocations:用于指定Mapper文件存放的位置

configLocation:用于指定Mybatis的配置文件位置。如果指定了该属性,那么 会以该配置文件的内容作为配置信息构建对应的 SqISessionFactoryBuilder,但是后续属性指定的内容会覆盖该配置文件里面指定的对应内容

typeAliasesPackage:它一般对应我们的实体类所在的包,这个时候会自动取对应包中不包括包名的简单类名作为包括包名的别名。多个package 之间可以用逗号或者分号等来进行分隔

typeAliases:数据类型别名。指定了这个属性后,Mybatis会把这个类型的短名称作为这个类型的别名

7)修改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>

<typeAliases>

<typeAlias type="cn.itcast.entity.Dept" alias="Dept" />

</typeAliases>

<mappers>

<mapper resource="cn/itcast/entity/DeptMapper.xml" />

</mappers>

</configuration>

8)编写dao层接口及实现

DeptDao.java

public interface DeptDao {

//根据部门ID查询部门信息

public Dept selectOne(int deptId);

}

修改接口实现类DeptDaoImpl.java

添加SqlSessionTemplate成员属性及其setter方法,Spring配置文件中将为其注入对象;

使用SqlSessionTemplate对象进行增删改查操作。

public class DeptDaoImpl implements DeptDao{

private SqlSessionTemplate sqlSessionTemplate;

public SqlSessionTemplate getSqlSessionTemplate() {

return sqlSessionTemplate;

}

public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {

this.sqlSessionTemplate = sqlSessionTemplate;

}

//根据部门ID查询部门信息

public Dept selectOne(int deptId){

System.out.println("dao :"+deptId);

//SqlSession session=null;

Dept dept=null;

try {

dept=sqlSessionTemplate.selectOne("cn.itcast.entity.DeptMapper.selectOne",deptId);

System.out.println("dao.dept:"+dept);

} catch (Exception e) {

e.printStackTrace();

}

return dept;

}

}

9)编写业务层代码

业务层接口略,这里只写业务层实现类:DeptServiceImpl.java

public class DeptServiceImpl {

private DeptDao deptDao;

public Dept selectOne(int deptId){

Dept dept=deptDao.selectOne(deptId);

return dept;

}

public DeptDao getDeptDao() {

return deptDao;

}

public void setDeptDao(DeptDao deptDao) {

this.deptDao = deptDao;

}

}

10)配置bean信息到sping配置文件

<!-- DAO层部门信息表的数据操作对象 -->

<bean id="deptDao" class="cn.itcast.dao.impl.DeptDaoImpl" >

<property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>

</bean>

<!-- 业务层部门信息业务处理对象 -->

<bean id="deptService" class="cn.itcast.service.impl.DeptServiceImpl">

<property name="deptDao" ref="deptDao"/>

</bean>

11)编写测试类

@Test

public void selectOne() {

ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");

DeptServiceImpl deptService=(DeptServiceImpl)context.getBean("deptService");

Dept dept = deptService.selectOne(1);

System.out.println("dept:" + dept);

}

简化步骤

扫描式加载SQL映射文件

修改myBatis-config.xml文件,去掉<mappers>配置

<?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>

<typeAliases>

<typeAlias type="cn.itcast.entity.Dept" alias="Dept" />

</typeAliases>

    <!-- 采用扫描式加载映射文件,以下将不用配置,可以减少映射文件过多时维护的麻烦 -->

<!-- <mappers>

<mapper resource="cn/itcast/entity/DeptMapper.xml" />

</mappers>

    -->

</configuration>

修改applicationContext.xml,为SqlSessionFactoryBean设置mapperLocations属性

<!-- 配置session工厂 -->

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<property name="dataSource" ref="dataSource" />

<property name="configLocation" value="classpath:myBatis-config.xml" />

<!-- 配置扫描式加载SQL映射文件 -->

<property name="mapperLocations" value="classpath:cn/itcast/entity/*.xml"/>

</bean>

MapperScannerConfigurer简化配置

1) 在spring配置文件中添加MapperScannerConfigurer 配置并去掉所有的Dao接口实现类配置

<!-- 配置转换器(根据指定包批量扫描Mapper接口并生成实例(MapperFactoryBean))。

对于在basePackage设置的包(包括子包)下的接口类的全类名和在Mapper.xml文件中定义过的命名空间一致,

该bean调用SqlSession的getMapper方法生成对应的代理对象在调用Mapper接口的地方通过@Autowired方式将可以注入接口实例)

 (代理对象默认的id是首字母小写之后的接口名,也可以使用 @Repository在Mapper接口上重命名)-->

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

<!-- 达内曰:sqlSessionFactory可不指定,会以Autowired方式自动注入 -->

<property name="sqlSessionFactory" ref="sqlSessionFactory"/>

<property name="basePackage" value="cn.itcast.dao"/>

</bean>

<!-- DAO层部门信息表的数据操作对象,上面如果配置MapperScannerConfigurer转换器,DAO接口将不再使用实现类 -->

<!--

 <bean id="deptDao" class="cn.itcast.dao.impl.DeptDaoImpl" >

<property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>

</bean>

 -->

<!-- 业务层部门信息业务处理对象 -->

<bean id="deptService" class="cn.itcast.service.impl.DeptServiceImpl">

<!-- 上面如果配置MapperScannerConfigurer转换器,DAO接口将不再使用实现类注入 -->

<!-- <property name="deptDao" ref="deptDao"/>  -->

</bean>

MapperScannerConfigurer

在使用MapperFactoryBean时,有一个Mapper就需要定义一个对应的MapperFactoryBean。当遇到大量Mapper时就需要使用mybatis-spring.jar提供的MapperScannerConfigurer组件,通过这个组件会自动扫描各个Mapper接口,并注册对应的MapperFactoryBean对象。

在定义MapperScannerConfigurer时,只需要指定一个basePackage即可。basePackage用于指定Mapper接 口所在的包,在这个包及其所有子包下面的Mapper接口都将被搜索到,并把它们注册为一个个 MapperFactoryBean对象。多个包之间可以使用逗号或者分号进行分隔。

如果指定的某个包下并不完全是我们定义的Mapper 接口,此时使用MapperScannerConfigurer的另外两个 属性可以缩小搜索和注册范围,一个是annotationClass ,另一个是markerlnterface。

annotationClass:用于指定一个注解标记,当指定了annotationClass属性时,MapperScannerConfigurer将只注册使用了annotationClass注解标记的接口

markerlnterface:用于指定一个接口,当指定了markerlnterface属性时,MapperScannerConfigurer将只注册继承自markerlnterface的接口。

例:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

<property name="basePackage" value="org.tarena.dao"/>

<property name="annotationClass" value="org.tarena.annotation.MyBatisRepository"/>

</bean>

public @interface MyBatisRepository{

}

上例配置的含义:MapperScannerConfigurer自动扫描org.tarena.dao包下所有接口,遇到带@MyBatisRepository注解的将自动为其注册MapperFactoryBean

2) 检查或修改DeptMapper.xml文件:

注意:命名空间+id和接口+方法名 一致

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

<!-- 这时的命名空间就需要和dao接口类全类名一致了 -->

<mapper namespace="cn.itcast.dao.DeptDao">

<!-- 表字段和对应实体属性命名一致时可以不配置 -->

<resultMap id="deptResultMap" type="Dept">

<id property="deptId" column="dept_id" />

<result property="deptName" column="dept_name" />

<result property="deptAddress" column="dept_address" />

</resultMap>

<!-- 这时的id就需要和dao接口的方法一致了 -->

<select id="selectOne" parameterType="int" resultMap="deptResultMap">

select * from dept where dept_id=#{id}

</select>

</mapper>

3)业务类中,使用@Autowired为DAO接口注入对象

public class DeptServiceImpl {

@Autowired

private DeptDao deptDao;

//省略其它代码

4)删除Dao实现类(存在也没有意义)

5)只扫描特定的Mapper映射器的方法:

step1. 自定义一个注解。

step2. 将该注解添加到要扫描的映射器前面。

step3. 配置MapperScannerConfigurer,注入 annotationClass属性值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值