首页 > 技术文章 > mybatis学习-day01

likeguang 2022-01-02 19:30 原文

目录

0、前言

我觉得还是原生的Mybatis是最好用的!以至于现在不喜欢去使用Mybatis-plus等等工具信息,虽然说方便我们来书写代码,但是对于我们看源码和了解背后原理是毫无意义的。

个人建议:我是不建议上来就去参考官方文档的,首先应该会使用之后。使用熟练之后,然后再去来参考使用官方文档来写代码,这样的使用方式才是最佳的。

一、Mybatis概述

1. 框架简介

什么是框架

  • 框架:是整个或部分应用的可重用设计,是可定制化的应用骨架。它可以帮开发人员简化开发过程,提高开发效率。
  • 完成一个系统的时候:
    • 跟业务需求无关,而又不得不写的代码:==>封装成框架,由框架帮我们实现
    • 实现业务需求的功能代码:我们自己在框架基础上进行编写代码
  • 简而言之,框架是一个应用系统的半成品,开发人员在框架的基础上,根据业务需求开发功能。即:别人搭台,你唱戏。

框架解决了什么问题

  • 框架主要是解决了技术整合问题

​ 一个应用系统,必定要选用大量的技术来完成业务需求,实现功能。这就导致应用系统依赖于具体的技术,一旦技术发生了变化或者出现问题,会对应用系统造成直接的冲击,这是应该避免的。

​ 框架的出现,解决了这个问题:框架是技术的整合。如果使用了框架,在框架基础上进行开发,那么开发人员就可以直接调用框架的API来实现功能,而不需要关注框架的底层具体使用了哪些技术。这就相当于框架“屏蔽”了具体的技术,实现了应用系统功能和技术的解耦。

  • 框架一般处于低层应用平台(如JavaEE)和高层业务逻辑之间

有哪些常见的框架

SSM:SpringMVC + Spring + MyBatis
  • SpringMVC:web层,负责和客户端交互

  • Spring:三层解耦,还负责事务管理

  • Mybatis:持久层,负责数据库访问

SSH:Struts + Spring + Hibernate
  • Struts1/2:web层,负责和客户端交互
  • Spring:三层解耦,还负责事务管理
  • Hibernate:持久层,负责数据库访问

2. Mybatis简介

Mybatis介绍

​ Mybatis是一个优秀的Java轻量级持久层框架。

ibatis:internet abatis,是Apache的框架,后来迁移到了google code,后来代码迁移到了github上

  • 它内部封装了JDBC,使开发人员只需要关心SQL语句,而不需要处理繁琐的JDBC步骤
  • 它采用了ORM思想,解决了实体和数据库映射的问题。只要提供好sql语句,配置了映射,Mybatis会自动根据参数值动态生成SQL,执行SQL并把结果封装返回给我们。
  • 它支持XML和注解两种方式配置映射。

ORM思想

​ ORM:Object Relational Mapping,对象关系映射思想。指把Java对象和数据库的表和字段进行关联映射,从而达到操作Java对象,就相当于操作了数据库。查询了数据库,自动封装成JavaBean对象

​ Mybatis是一个半ORM框架:

  • 在查询时,Mybatis会自动把查询的数据,帮我们封装成Java对象:关系==>对象 OK

  • 在增删改时,Mybatis不能帮我们把JavaBean的数据,自动映射到表字段里:对象==>关系 不行

3. Mybatis下载

二、快速入门

需求描述

查询所有用户信息,获取用户集合List<User>

准备工作

1、实现步骤

  1. 创建Java项目,导入依赖,准备JavaBean

  2. 编写Mybatis的代码,查询所有用户。

  3. 编写测试代码

功能实现

1 创建Maven项目,准备JavaBean

1) 创建java项目,导入依赖
<dependencies>
    <!--Junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--MySql的数据库驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.46</version>
    </dependency>
    <!--Mybatis的jar包-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
    <!--Mybatis依赖的日志包-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>
2) 创建JavaBean
  • com.guang.pojo里创建User
public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    //get/set...
    //toString...
}

2 编写Mybatis的代码,查询所有用户

1) 创建dao接口(映射器)

​ dao层的接口叫映射器,映射器的类名,可以叫XXXMapper,也可以叫XXXDao。我们这里按照之前的习惯,取名UserDao

​ 注意:只要创建接口即可,不需要创建接口的实现类。因为这里最终得到的是代理对象,JDK动态生成的。

public interface UserDao {
    List<User> queryAll();  
}
2) 准备映射文件xml

在resources目录下创建文件的时候,一定要注意路径:

com/guang/UserDao.xml
  1. 映射文件名称要和映射器类名一样。例如:映射器叫UserDao,那么配置文件就叫UserDao.xml

  2. 映射文件位置要和映射器位置一样

<?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.guang.dao.UserDao">
    <!--select标签:表示要执行查询语句; id:给接口里哪个方法配置的,写方法名;resultType:结果集封装类型-->
    <select id="queryAll" resultType="com.guang.pojo.User">
        select * from user
    </select>
</mapper>
3) 准备Mybatis的日志配置文件

​ Mybatis支持使用log4j输出执行日志信息,但是需要我们提供log4j的配置文件:log4j.properties

注意:

  1. 如果没有log4j.properties,不影响Mybatis的功能,只是没有详细日志而已
  2. 如果需要日志的话,要把log4j.properties文件放到src目录下。log4j.properties内容如下:
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE            debug   info   warn error fatal
log4j.rootCategory=debug, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
4) 准备Mybatis的全局配置文件xml
  1. 全局配置文件的名称随意,我们习惯叫 SqlMapConfig.xml
  2. 全局配置文件的位置随意,我们习惯放到src的根目录下
<?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>
    <!--配置默认的数据库环境-->
    <environments default="mysql_mybatis">
        <!--定义一个数据库连接环境-->
        <environment id="mysql_mybatis">
            <!--设置事务管理方式,固定值JDBC-->
            <transactionManager type="JDBC"/>
            <!--设置数据源,POOLED,UNPOOLED,JNDI,我们使用POOLED-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!--  !!!配置映射文件的位置!!!!!!!!!!!!!!这一步千万不要忘记!!!!!!!!!!!!!!  -->
    <mappers>
        <mapper resource="com/guang/dao/UserDao.xml"/>
    </mappers>
</configuration>

3 编写测试代码

public class MybatisTest {
	@Test
    public void testQuickStart() throws IOException {
        //1. 读取全局配置文件SqlMapConfig.xml
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactoryBuilder构造者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3. 使用构造者builder,根据配置文件的信息is,构造一个SqlSessionFactory工厂对象
        SqlSessionFactory factory = builder.build(is);
        //4. 使用工厂对象factory,生产一个SqlSession对象
        SqlSession session = factory.openSession();
        //5. 使用SqlSession对象,获取映射器UserDao接口的代理对象
        UserDao dao = session.getMapper(UserDao.class);
        //6. 调用UserDao代理对象的方法,查询所有用户
        List<User> users = dao.queryAll();
        for (User user : users) {
            System.out.println(user);
        }
        //7. 释放资源
        session.close();
        is.close();
    }
}

小结

  1. 在dao包里创建映射器,在接口里写方法
    • 映射器:dao层的接口。不要实现类
  2. 在resource里创建映射文件
    • 映射文件名称 必须和映射器名称相同
    • 映射文件位置 必须和映射器位置相同
    • 给每个方法配置sql语句
  3. 在resources里创建全局配置文件
    • 名称随意,位置建议放到resources里
    • 里边配置数据库环境,所有的映射文件路径
  4. 准备log4j.properties

MyBatis涉及的设计模式:

*      构造者模式:用于隐藏复杂的构造过程,帮我们构造一个对象出来
*      工厂模式:由工厂帮我们生产一些对象,目的是解耦
*      代理模式:在不能直接调用目标对象,或者不方便直接调用时,使用代理模式生成的代理对象来间接调用

三、代理方式CURD

需求说明

针对user表进行CURD操作:

  • 查询全部用户,得到List<User>
  • 保存用户(新增用户)
  • 修改用户
  • 删除用户
  • 根据主键查询一个用户,得到User
  • 模糊查询
  • 查询数量

准备工作

在单元测试类中准备好@Before@After的方法代码备用。编写完成一个功能,就增加一个@Test方法即可。

代码如下:

public class MybatisCURDTest {
    private InputStream is;
    private SqlSession session;
    private UserDao dao;

    @Before
    public void init() throws IOException {
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        session = factory.openSession();
        dao = session.getMapper(UserDao.class);
    }

    @After
    public void destory() throws IOException {
        //释放资源
        session.close();
        is.close();
    }
}

新增用户

1) 在映射器中增加方法

  • UserDao中增加方法
void save(User user);

2) 在映射文件中添加配置

  • UserDao.xml中增加配置
    • SQL语句中#{}代表占位符,相当于预编译对象的SQL中的?,具体的值由User类中的属性确定
<!--parameterType:是方法的参数类型,写全限定类名-->
<insert id="save" parameterType="com.guang.pojo.User">
    <!-- 
            selectKey标签:用于向数据库添加数据之后,获取最新的主键值
                resultType属性:得到的最新主键值的类型
                keyProperty属性:得到的最新主键值,保存到JavaBean的哪个属性上
                order属性:值BEFORE|AFTER,在insert语句执行之前还是之后,查询最新主键值。MySql是AFTER
         -->
    <selectKey resultType="int" keyProperty="id" order="AFTER">
        select last_insert_id()
    </selectKey>

    insert into user (id, username, birthday, address, sex) 
    values (#{id}, #{username}, #{birthday},#{address},#{sex})
</insert>

3) 编写测试代码

  • 在单元测试类里增加测试方法
  • 注意:执行了DML语句之后,一定要提交事务:session.commit();

那么这里来说一下,在哪里将自动提交设置成了false呢?

看一下关键步骤:

SqlSession session = factory.openSession();

然后跟进去,来到org.apache.ibatis.session.defaults.DefaultSqlSessionFactory

  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

可以看到将最后一个参数,也就是autoCommit设置成了false,还可以一直跟到事务的地方,可以看到是将事务那里也设置了关闭自动提交

    @Test
    public void testSaveUser(){
        User user = new User();
        user.setUsername("tom");
        user.setAddress("广东深圳");
        user.setBirthday(new Date());
        user.setSex("男");

        System.out.println("保存之前:" + user);//保存之前,User的id为空
        dao.save(user);
        session.commit();//注意:必须要手动提交事务
        System.out.println("保存之后:" + user);//保存之后,User的id有值
    }

修改用户

1) 在映射器中增加方法

  • UserDao中增加方法
void edit(User user);

2) 在映射文件中添加配置

  • UserDao.xml中增加配置
<update id="edit" parameterType="com.guang.pojo.User">
    update user set username = #{username}, birthday = #{birthday}, 
    address = #{address}, sex = #{sex} where id = #{id}
</update>

3) 编写测试代码

  • 注意:执行了DML语句之后,一定要提交事务:session.commit();
@Test
public void testEditUser(){
    User user = new User();
    user.setId(50);
    user.setUsername("jerry");
    user.setAddress("广东深圳宝安");
    user.setSex("女");
    user.setBirthday(new Date());

    dao.edit(user);
    session.commit();
}

删除用户

1) 在映射器中增加方法

  • UserDao中增加方法
void delete(Integer id);

2) 在映射文件中增加配置

  • UserDao.xml中增加配置

  • 注意:

    • 如果只有一个参数,且参数是简单类型的,那么#{id}中的id可以随意写成其它内容,例如:#{abc}
    • 简单类型参数:8种基本数据类型及其包装类, 和字符串
<delete id="delete" parameterType="int">
    delete from user where id = #{id}
</delete>

3) 编写测试代码

  • 在单元测试类里增加方法

  • 执行了DML语句之后,一定要提交事务:session.commit();

@Test
public void testDeleteUser(){
    dao.delete(50);
    session.commit();
}

查询功能

根据主键查询一个用户

1) 在映射器中增加方法
  • UserDao中增加方法
User findById(Integer id);
2) 在映射文件中增加配置
  • UserDao.xml中增加配置
<select id="findById" parameterType="int" resultType="com.guang.pojo.User">
    select * from user where id = #{id}
</select>
3) 编写测试代码
  • 在单元测试类里增加方法
@Test
public void testFindUserById(){
    User user = dao.findById(48);
    System.out.println(user);
}

模糊查询-#{}方式

1 在映射器中增加方法
/**
 * 使用#{}方式进行模糊查询
 */
List<User> findByUsername1(String username);
2 在映射文件中增加配置
  • 注意:只有一个参数,且是简单参数时, #{}中可以写成其它任意名称,例如:#{abc}
<select id="findByUsername1" parameterType="string" resultType="com.guang.pojo.User">
    select * from user where username like #{username}
</select>
3 编写测试代码
  • 注意:模糊查询的条件值,前后需要有%
/**
 * 使用#{}方式进行模糊查询--单元测试方法
 */
@Test
public void testFindUserByUsername1(){
    List<User> users = dao.findByUsername1("%王%");
    for (User user : users) {
        System.out.println(user);
    }
}

模糊查询-${}方式

1 在映射器中增加方法
/**
 * 使用${value}方式进行模糊查询
 */
List<User> findByUsername2(String username);
2 在映射文件中增加配置
  • Mybatis3.4版本中${value}是固定写法,不能做任何更改(高版本Mybatis#{}中可以随意写)
  • 在SQL语句中已经加了%,那么在测试代码中,传递参数值时就不需要再加%了
<select id="findByUsername2" parameterType="string" resultType="com.guang.pojo.User">
   select * from user where username like '%${value}%'
</select>
3 编写测试代码
/**
 * 使用${value}方式进行模糊查询--单元测试方法
 */
@Test
public void testFindUserByUsername2(){
    //注意:SQL语句中已经加了%, 参数值前后不需要再加%
    List<User> users = dao.findByUsername2("王");
    for (User user : users) {
        System.out.println(user);
    }
}

#{}${}的区别【面试题】

  • #{}:表示一个占位符,相当于预编译对象的SQL中的?

    • 可以有效防止SQL注入;
    • Mybatis会自动进行参数的Java类型和JDBC类型转换;
    • #{}写的是属性名称。如果只有一个参数并且是简单类型,里边可以是value或者其它名称
    映射文件中的SQL:select * from user where username like #{username}
    单元测试中传递实参:%王%
    
    最终执行的SQL:select * from user where username like ?
    参数值:%王%
    
  • ${}:表示拼接SQL串,相当于把实际参数值,直接替换掉${}

    • 不能防止SQL注入

    • Mybatis不进行参数的Java类型和JDBC类型转换

    • ${}写的是属性名称。如果只有一个参数并且是简单类型,${value}中只能是value,不能是其它名称

      (Mybatis3.4 里存在这种现象,3.5没也可以随意写了)

    映射文件中的SQL:select * from user where username like '%${value}%'
    单元测试中传递实参:王
    
    最终执行的SQL:select * from user where username like '%王%'
    

查询数量

1) 在映射器UserDao中增加方法
Integer findTotalCount();
2) 在映射文件中UserDao.xml增加配置
<select id="findTotalCount" resultType="int">
    select count(*) from user
</select>
3) 在单元测试类中编写测试代码
@Test
public void testFindTotalCount(){
    Integer totalCount = dao.findTotalCount();
    System.out.println(totalCount);
}

功能小结

  • 使用Mybatis开发的步骤:

    1. 在映射器里增加方法
    2. 在映射文件里增加配置
  • 在映射文件里:

    • 有四种标签,用于配置不同的语句:
      • select:查询标签。
        • id:对应的方法名称
        • parameterType:参数类型
        • resultType:查询结果集封装的类型
      • insert:插入
        • id:对应的方法名称
        • parameterType:参数类型
      • update:修改
        • id:对应的方法名称
        • parameterType:参数类型
      • delete:删除
        • id:对应的方法名称
        • parameterType:参数类型
    • 四种标签里边写的是sql语句
      • 使用 #{}获取参数值,使用 ${}获取参数值
      • 两者的区别是:
        • #{}:本质是占位符,使用预编译的方式
          • 可以防止 sql注入漏洞
          • 可以自动进行Java类型和JDBC类型的转换
          • 如果只有一个参数,并且是简单类型,#{这里可以随意写}
        • ${}:本质是字符串的拼接,没有使用预编译
          • 不能防止sql注入
          • 不能自动进行Java类型和JDBC类型的转换
          • 如果只有一个参数,并且是简单类型,${value}

四、参数和结果集

1. OGNL简介

  • OGNL:Object Graphic Navigator Language,是一种表达式语言

  • OGNL参考官网地址http://commons.apache.org/proper/commons-ognl/language-guide.html

  • #{}里、${}可以使用OGNL表达式,可以:

    • 从JavaBean中获取指定属性的值,本质上使用的是JavaBean的getXxx()方法。
    • 进行数据运算
    • 调用对象的方法和属性
    • ... ...
  • 例如:

    • user.getUsername()---> user.username

    • user.getAddress().getProvince()--->user.address.province

    • list.size()>0, list != nullarray.length > 0, str.length() > 0

2. parameterType

parameterType用来配置方法的参数类型,通常可以不写。

不同的参数类型和个数,会影响sql语句中获取参数值的方式。

方法有一个参数

参数是简单类型
  • 简单参数:指8种基本数据类型及其包装类, 或者字符串
  • 参数写法:
    • 包装类全限定类名:java.lang.Integer, java.lang.Double等等
    • 基本数据类型名称:int, double, short 等基本数据类型
    • 字符串:string, 或 java.lang.String
  • SQL语句里获取参数:如果是一个简单类型参数,写法是:#{随意}
List<User> findByUsername(String username);
<select id="findByUsername" resultType="com.guang.pojo.User">
    <!-- 只有一个参数,并且是简单类型,那么 #{}里可以随意写 -->
    select * from user where username like #{username}
</select>
参数是POJO(JavaBean)
  • 参数写法:要写全限定类名

    • parameterType写全限定类名com.guang.pojo.User
  • SQL语句里获取参数:#{JavaBean的属性名}

List<User> findByUser(User user);
<select id="findByUser" parameterType="com.guang.pojo.User" resultType="com.guang.pojo.User">
    <!-- 只有一个参数,参数是JavaBean,那么 #{}里写JavaBean的属性名 -->
    select * from user where username like #{username} and sex = #{sex}
</select>
参数是POJO包装类(复杂JavaBean)

注意:这个也是很有用的一个点。经常在前端的搜索栏中需要使用到这个点。

  • 参数写法:

​ 在web应用开发中,通常有综合条件的搜索功能,例如:根据商品名称 和 所属分类 同时进行搜索。这时候通常是把搜索条件封装成JavaBean对象;JavaBean中可能还有JavaBean。

  • SQL里取参数:#{xxx.xx.xx}

  • 例如:根据用户名搜索用户信息,查询条件放到QueryVO的user属性中。QueryVO如下:

public class QueryVO {
    private User user;
    //private String className;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}
1) 在映射器UserDao中增加方法
List<User> findByVO(QueryVO vo);
2) 在映射文件中增加配置信息
<select id="findByVO" parameterType="com.guang.pojo.QueryVO"     resultType="com.guang.pojo.User">
   select * from user where username like #{user.username}
</select>
3) 在单元测试类中编写测试代码
@Test
public void testFindByVO(){
    QueryVO vo = new QueryVO();
    User user = new User();
    user.setUsername("%王%");
    vo.setUser(user);

    List<User> users = dao.findByVO(vo);
    for (User user1 : users) {
        System.out.println(user1);
    }
}
参数是Map
List<User> findByMap(Map<String,Object> map);
<select id="findByMap" parameterType="java.util.Map" resultType="User">
    <!-- 参数是Map,那么 #{}里写Map的key -->
    select * from user where user_name like #{username} and sex = #{sex}
</select>
    @Test
    public void testFindByMap(){
        Map<String, Object> map = new HashMap<>();
        map.put("username", "%王%");
        map.put("sex", "男");

        List<User> userList = userDao.findByMap(map);
        for (User user : userList) {
            System.out.println(user);
        }
    }
参数是List
List<User> findByList(List<Integer> ids);
<select id="findByList" resultType="User">
    <!-- 参数是List,那么#{}里写  list[索引],或者 collection[索引] -->
    select * from user where id in(#{list[0]}, #{list[1]})
</select>
	@Test
    public void testFindByList(){
        List<Integer> ids = new ArrayList<>();
        ids.add(41);
        ids.add(42);
        List<User> userList = userDao.findByList(ids);
        for (User user : userList) {
            System.out.println(user);
        }
    }

方法有多个参数

默认取值方式
List<User> findByNameAndSex(String name, String sex);
<select id="findByNameAndSex" resultType="User">
    <!-- 
		如果有多个参数,那么默认情况下 #{}里可以写:
			arg0表示第一个参数, arg1表示第二个参数, 以此类推
			或者
			param1表示第一个参数,param2表示第二个参数,以此类推
	-->
    select * from user where user_name like #{arg0} and sex = #{arg1}
</select>
命名参数方式
List<User> findByNameAndSex2(@Param("username") String name, 
                             @Param("sex") String sex);
<select id="findByNameAndSex2" resultType="User">
    <!-- 
	如果多个参数:
       如果方法参数上有@Param注解,那么可以写:#{参数名}或 #{param1}
       或者
       如果方法参数上没有@Param注解,那么可以写:#{arg0}或#{param1}表示第一个参数,以此类推
	-->
    select * from user where user_name like #{username} and sex = #{sex}
</select>

源码分析【拓展】

如果有多个参数,Mybatis会把参数封装成Map

  • 以实参参数名为key,以参数值为value,放到Map里
  • 以param+序号为key,以参数值为value,放到Map里

所以在sql里获取参数时才可以写:#{arg0}, #{arg1}, #{param1}, #{param2}

ParamNameResolver的构造方法
  private static final String GENERIC_NAME_PREFIX = "param";

  /**
   * key是索引,value是参数名称
   * 参数名称:
   * 	如果配置了@Param,就是@Param配置的名称
   *    如果没有@Param,就以参数索引为名称
   */
  private final SortedMap<Integer, String> names;


  public ParamNameResolver(Configuration config, Method method) {
    final Class<?>[] paramTypes = method.getParameterTypes();
      //获取每个方法参数上的注解。一个方法有多个参数,一个参数可能有多个注解,所以以二维数组接收
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
    int paramCount = paramAnnotations.length;
    // 循环处理方法上的每个参数
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
        //循环处理参数上的每个注解。如果有@Param注解,就以@Param指定的值为参数名称
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
        //如果没有获取到@Param配置的名称,就获取方法的参数名称
        //按照jdk规范,反射获取的方法参数名称 默认是arg0、arg1...
      if (name == null) {
        // @Param was not specified.
        if (config.isUseActualParamName()) {
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }
ParamNameResolver的getNamedParams()方法
//args:是调用映射器方法时,传递进去的参数值
public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    //如果没有参数值,返回null
    if (args == null || paramCount == 0) {
      return null;
        
    //如果只有一个参数,并且没有@Param注解,把参数值直接返回,未命名
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];
        
    //如果有多个参数,或者参数上有@Param注解
    } else {
        //创建一个Map,把所有参数放到Map里,返回这个Map
      final Map<String, Object> param = new ParamMap<Object>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
          //以构造方法里解析得到的参数名为key,以参数值为value,放到map里
        param.put(entry.getValue(), args[entry.getKey()]);
          
        //以 param+序号 为key,以参数值为value,放到Map里
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }
DefaultSqlSession的wrapCollection()方法
  private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      //如果参数值是Collection类型的,以collection为key,以参数值为value,放到map里        
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("collection", object);
      if (object instanceof List) {
          //如果参数值是List类型的,再以list为key,以参数值为value,放到map里
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
        //如果参数值是数组,就以array为key,以参数值为value,放到map里
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("array", object);
      return map;
    }
    return object;
  }

小结

  • 如果只有一个参数:
    • 参数是简单类型,#{}随意写
    • 参数是POJO,#{POJO的属性名}
    • 参数是Map,#{map的key}
    • 参数是List,#{list[索引]}或者 #{collection[索引]}
  • 如果有多个参数:
    • 默认情况下:
      • #{arg0}, #{arg1}, ...获取参数。仅适用于参数上没有@Param注解的情况
      • #{param1},#{param2},...获取参数
    • 使用@Param注解声明参数名称
      • #{参数名称}
      • #{param1},#{param2},...获取参数

3.规范

在阅读了上面的源码之后,我觉得这里应该来建立起来一个规范。

不管方法参上有多少个注解,是否是基础类型,那么在使用的时候都应该来建立对应的开发规范。因为ONGL表达式可以来做对应的操作。

所以Dao接口中使用的时候,都需要在参数位置上添加上@Param注解,来指定参数的名字;在xml文件中进行书写的时候直接利用ONGL表达式来进行书写即可。我可能在这个地方有点强迫症,我只看对应的接口方法中的@Param注解来进行开发。

那么来演示一下上面的操作:

方法上只有一个参数

参数是简单类型
List<User> findByUsername(@Param("username") String username);

对应的xml文件中书写:

    <select id="findByUsername" parameterType="string" resultType="com.guang.pojo.User">
        select * from user where username = #{username}
    </select>
参数是POJO包装类(复杂JavaBean)

接口方法:

    /**
     * 根据用户的姓名和性别来查询对应的用户集合
     * @param user
     * @return
     */
    List<User> findByUser(@Param("user") User user);

对应的xml文件

    <select id="findByUser" parameterType="com.guang.pojo.User" resultType="com.guang.pojo.User">
        select * from user where username = #{user.username} and sex = #{user.sex}
    </select>
参数是POJO包装类(复杂JavaBean)

对应的实体类对象:

public class QueryVO {
    private User user;
	// getter/setter方法
}

对应的接口

    List<User> findByVO(@Param("queryVo") QueryVO vo);

对应的xml

    <select id="findByVO" parameterType="com.guang.pojo.QueryVO" resultType="com.guang.pojo.User">
        select * from user where username = #{queryVo.user.username}
    </select>
参数是Map

当参数是map的时候应该注意了,因为这里在xml中需要将key作为对应的主键,所以应该先写key,然后再向xml文件中来写对应的值。

接口

    /**
     * 根据map中的key来构成条件来进行条件。所以这里需要将key确定下来,所以这里一般说来,也很少会有人来使用这种方式来进行操作
     * @param map 封装条件的map
     * @return
     */
    List<User> findByMap(@Param("map")Map<String,Object> map);

xml文件

    <select id="findByMap" parameterType="map" resultType="com.guang.pojo.User">
        select * from user where username like #{map.username} and sex=#{map.sex}
    </select>

对应的测试:

    /**
     * 当map中的条件回写完成之后,应该立刻马上去写xml文件中的key
     */
    @Test
    public void testFindByMap(){
        // 利用map来构建条件
        Map<String, Object> map = new HashMap<>();
        map.put("username", "%王%");
        map.put("sex", "男");
        List<User> userList = dao.findByMap(map);
        for (User user : userList) {
            System.out.println(user);
        }
    }
参数是List或者是array

下面的foreach标签会在下一篇章来进行介绍。

是list或者是array套路都是一样的。唯一不同的一点就是参数位置,一个是list,一个是数组而已

    List<User> findByList(@Param("ids") List<Integer> ids);

对应的xml文件

    <select id="findByList" parameterType="list" resultType="com.guang.pojo.User">
        select * from user where id in
        <foreach collection="ids" item="item" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </select>

测试:

    @Test
    public void testfindByList(){
        List<Integer> integerList = Arrays.asList(41, 42, 43);
        List<User> userList = dao.findByList(integerList);
        userList.forEach(System.out::println);
    }

可以看到控制台输出的SQL语句即可。

那么看下数组的方式

接口:

    List<User> findByArray(@Param("ids")Integer[] ids);

对应的xml文件

    <select id="findByArray" parameterType="object" resultType="com.guang.pojo.User">
        select * from user where id in
        <foreach collection="ids" item="item" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </select>

测试类:

    @Test
    public void testfindByArray(){
        Integer[] integers = new Integer[3];
        integers[0]=41;
        integers[1]=42;
        integers[2]=43;
        List<User> userList = dao.findByArray(integers);
        userList.forEach(System.out::println);
    }

一般来说,这种查询都很少来进行使用。尤其是数组类型的,几乎都不会来这么使用的。

4. 结果集映射

如果执行的是查询,那么Mybatis会帮我们把查询的结果集自动映射成指定的类型。我们可以在select标签上使用resultType属性进行自动映射, 或使用resultMap属性来手动设置映射关系。

  • resultType

    设置查询结果集的封装类型,Mybatis会把查询结果自动映射到指定的类型上。

    但是要注意:如果resultType设置为JavaBean,要求JavaBean的属性名和字段名保持一致,否则不能封装

  • resultMap

    用于手动设置字段和JavaBean的映射关系,它非常灵活也非常强大 【重点】

resultType自动映射

resultType是简单类型

简单类型指:8种基本数据类型及其包装类,和字符串

<select id="xxx" resultType="int"></select>
<select id="xxx" resultType="java.lang.Integer"></select>
<select id="xxx" resultType="java.lang.String"></select>
resultType是POJO类型

POJO指的是Plain Ordinary Java Object,简单Java对象,实际就是普通JavaBeans。

resultType的值是JavaBean时,要求JavaBean的属性名要和字段名保持一致,否则Mybatis不能自动映射

<select id="xxx" resultType="com.guang.pojo.User"></select>
驼峰式命名映射

数据库里字段命名,通常是是A_COLUMN风格的,而JavaBean的属性命名通常是驼峰式命名aColumn风格的。这种情况下,字段名和属性名并不一致,但是Mybatis仍然支持自动映射,而我们只要开启一个全局开关即可。

开启驼峰式命名映射
  • 修改全局配置文件,开启下划线和驼峰式命名映射的开关
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
属性名和字段名不一致

User2如下,属性名和数据库表的字段名不同。要求查询user表的所有数据,封装成User2的集合。

public class User2 implements Serializable{
    private Integer userId;
    private String username;
    // jdk8中支持将LocalDate转换成date类型
    private Date userBirthday;
    private String userSex;
    private String userAddress;

    //get/set...
    //toString...
}

resultType是POJO类型时,要求POJO属性名和字段名必须一致,Mybatis才能自动映射成功。那么如果不一致会出现什么情况,又该如何处理呢?

  • 方案一:SQL语句中给查询的列起别名,别名和JavaBean属性名保持一致

  • 方案二:使用resultMap代替掉resultType,手动设置映射关系 【这种方式比较习惯来进行使用!】

这里我们先演示方案一:

1) 在映射器UserDao中增加方法
/**
 * JavaBean属性名和字段名不一致的情况处理---方案一
 * @return
 */
List<User2> queryAll_plan1();
2) 在映射文件UserDao.xml中增加statement
<select id="queryAll_plan1" resultType="com.guang.pojo.User2">
    select id as userId, username as username, birthday as userBirthday, address as userAddress, sex as userSex from user
</select>
3) 在单元测试类中编写测试代码
/**
 * JavaBean属性名和字段名不一致的情况处理---方案一 单元测试代码
 */
@Test
public void testQueryAllUser2_plan1(){
    List<User2> user2List = dao.queryAll_plan1();
    for (User2 user2 : user2List) {
        System.out.println(user2);
    }
}

resultMap手动映射

resultMapresultType是不能同时存在于select标签上的,只能二选一。

我们这里使用resultMap来解决 JavaBean属性名和字段名不一致的情况

1) 在映射器中增加方法
/**
 * JavaBean属性名和字段名不一致的情况处理--方案二
 * @return
 */
List<User2> queryAll_plan2();
2) 在映射文件中增加配置
<select id="queryAll_plan2" resultMap="user2Map">
    select * from user
</select>

<!-- 
 resultMap标签:设置结果集中字段名和JavaBean属性的对应关系
     id属性:唯一标识
   type属性:要把查询结果的数据封装成什么对象,写全限定类名 
 -->
<resultMap id="user2Map" type="com.guang.pojo.User2">
    <!--id标签:主键字段配置。  property:JavaBean的属性名;  column:字段名-->
    <id property="userId" column="id"/>
    <!--result标签:非主键字段配置。 property:JavaBean的属性名;  column:字段名-->
    <result property="username" column="username"/>
    <result property="userBirthday" column="birthday"/>
    <result property="userAddress" column="address"/>
    <result property="userSex" column="sex"/>
</resultMap>
3) 编写测试代码
/**
 * JavaBean属性名和字段名不情况处理--方案二  单元测试代码
 */
@Test
public void testQueryAllUser2_plan2(){
    List<User2> user2List = dao.queryAll_plan2();
    for (User2 user2 : user2List) {
        System.out.println(user2);
    }
}
小结
  • 映射器里加方法
  • 映射文件里加配置:使用resultMap代替掉resultType,两者不能共存
<select id="xxx" resultMap="resultMap的id">
	sql语句 
</select>
<resultMap id="resultMap的id" type="映射的JavaBean的全限定类名">
    <!-- id配置主键字段映射(建议一定要写上);result配置非主键字段映射 -->
    <id property="" column=""/>
    <result property="" column=""/>
</resultMap>

五、全局配置文件

SqlMapConfig.xml中配置的内容和顺序如下:

properties(属性)
settings(全局配置参数) 
typeAliases(类型别名) ★ 
typeHandlers(类型处理器) 
objectFactory(对象工厂) 
plugins(插件) 
environments(环境集合属性对象) 
environment(环境子属性对象) 
transactionManager(事务管理) 
dataSource(数据源) 
mappers(映射器) ★

1. typeAlias类型别名

​ 在映射文件中,我们要写大量的parameterType和resultType,如果全部都写全限定类名的话,代码就太过冗余,开发不方便。可以使用类型别名来解决这个问题。

​ 类型别名:是Mybatis为Java类型设置的一个短名称,目的仅仅是为了减少冗余。

​ 注意:类型别名不区分大小写

Mybatis内置别名

Mybatis提供的别名有:

别名 映射的类型(Java类型)
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string java.lang.String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

自定义类型别名

​ 自己定义的JavaBean,全限定类名太长,可以自定义类型别名。

给一个类指定别名
1) 配置一个类的别名
  • 在全局配置文件中,增加如下配置
    <typeAliases>
        <!-- type:要指定别名的全限定类名    alias:别名 -->
        <typeAlias type="com.guang.pojo.QueryVO" alias="vo"/>
        <typeAlias type="com.guang.pojo.User" alias="user"/>
        <typeAlias type="com.guang.pojo.User2" alias="user2"/>
    </typeAliases>
2) 在映射文件中使用类型别名
<!-- parameterType使用别名:vo, resultType使用别名:user -->    
<select id="findByVO" parameterType="vo" resultType="user">
    select * from user where username like #{user.username}
</select>
批量注册别名

我们可以使用package标签,指定一个包,Mybatis会把包里的类自动注册别名,别名即类名。

1) 为一个package下所有类注册别名
  • 在全局配置文件中增加如下内容:
<typeAliases>
    <!-- 把com.guang.pojo包下所有JavaBean都注册别名,类名即别名,不区分大小写 -->
    <package name="com.guang.pojo"/>
</typeAliases>
2) 在映射文件中使用类型别名
<!-- parameterType使用别名:queryvo, resultType使用别名:user -->    
<select id="findByVO" parameterType="queryvo" resultType="user">
    select * from user where username like #{user.username}
</select>
别名冲突的处理

如果使用了批量注册别名,那么可能多个包里的相同名称的类,例如有com.guang.pojo.Usercom.guang.pojo.User,这个时候两个类的别名都是user,就会造成冲突。

如何处理别名冲突呢?在Java类上使用注解@Alias指定别名即可:

package com.guang.pojo;

@Alias("user1")
public class User{}
package com.guang.pojo;

@Alias("user2")
public class User{}

2. mappers映射器

用来配置映射器接口的配置文件位置,或者映射器接口的全限定类名

2.1 <mapper resource=""/>

用于指定映射文件xml的路径,支持xml开发方式,例如:

<mappers>
	<mapper resource="com/guang/dao/UserDao.xml"/>
</mappers>

注意:

​ 映射文件的名称,和映射器接口类名 可以不同

​ 映射文件的位置,和映射器接口位置 可以不同

配置了xml的路径,Mybatis就可以加载statement信息,并且根据namespace属性找到映射器

2.2 <mapper class=""/>

用于指定映射器接口的全限定类名,支持XML开发和注解开发,例如:

<mappers>
	<mapper class="com.guang.dao.UserDao"/>
</mappers>

如果是使用xml方式开发,那么要注意:

​ 映射文件的名称 要和 映射器接口的类名相同

​ 映射文件的位置 要和 映射器接口的位置相同

Mybatis只知道映射器的名称和位置,不知道配置文件的名称和位置。只能查找同名同路径的配置文件

2.3 <package name=""/>

用于自动注册指定包下所有的映射器接口,支持XML开发和注解开发,例如:

<mappers>
	<package name="com.guang.dao"/>
</mappers>

如果是使用XML方式开发,那么要注意:

​ 映射文件的名称 要和 映射器接口的类名相同

​ 映射文件的位置 要和 映射器接口的位置相同

Mybatis只能根据包名找到所有的映射器的类名和位置, 不知道配置文件的名称和位置。只能查找同名同路径的配置文件

小结

  • 在全局配置文件里,加上映射器配置:
<mappers>
	<package name="com.guang.dao"/>
</mappers>
  • 注意:
    • 映射器接口名称、位置,必须和映射文件的名称、位置 相同

六、数据源和事务【了解】

1. Mybatis的数据源

全局配置文件,会被Mybatis封装成一个Configuration对象

  Mybatis中的数据源,是指全局配置文件中<dataSouce></dataSouce>的配置。Mybatis为了提高数据库操作的性能,也使用了连接池的技术,但是它采用的是自己开发的连接池技术。

1.1 Mybatis中dataSouce的分类

1.1.1 三种dataSouce介绍
  • UNPOOLED:不使用连接池技术的数据源

    对应Mybatis的UnpooledDataSouce类,虽然也实现了javax.sql.DataSource接口,但是它的getConnection()方法中,并没有真正的使用连接池技术,而是直接从数据库中创建的连接对象,即:DriverManager.getConnection()方法

  • POOLED:使用连接池技术的数据源

    对应Mybatis的PooledDataSouce类,它实现了javax.sql.DataSouce接口,同时采用了Mybatis自己开发的连接池技术,是我们使用最多的一种数据源

  • JNDI:使用JNDI技术的数据源

    采用服务器软件提供的JNDI技术实现,从服务器软件中获取数据源。从不同服务器软件中得到的DataSouce对象不同。例如:Tomcat中配置了数据连接信息,我们的web应用部署到Tomcat里,就可以获取Tomcat中配置的数据源,而不需要web应用自己再配置数据库连接信息。

1.1.2 三种dataSouce的关系与源码分析
  • UnpooledDataSourcePooledDataSource都实现了javax.sql.DataSource接口
  • UnpooledDataSource没有使用连接池技术,它的getConnection()方法是从数据库中创建的连接
  • PooledDataSource采用了连接池技术
    • 它内部有UnpooledDataSource的引用,当需要创建新连接时,是调用UnpooledDataSource来获取的
    • 它只是提供了用于存放连接对象的池子
1.1.3 PooledDataSource获取新连接的过程源码分析

1.2 Mybatis中dataSouce的配置

<dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql:///mybatis49"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</dataSource>

​ 当Mybatis读取全局配置文件时,会根据我们配置的dataSource标签的type,来生成对应的dataSource对象

2. Mybatis的事务

因为Mybatis的是对JDBC的封装,所以Mybatis在本质上也是基于Connection对象实现的事务管理,只是把管理的代码封装起来了,是使用SqlSession对象进行事务管理的。

默认事务管理方式

  默认情况下,我们使用工厂对象的openSession()方法得到的SqlSession对象,是关闭了事务自动提交的,即:默认情况下,SqlSession是开启了事务的,需要手动提交

​ 获取session对象:factory.openSession()

  操作完数据库之后,需要手动提交事务:sqlSession.commit();

  如果要回滚事务,就使用方法:sqlSession.rollback();

自动提交事务实现

Mybatis也支持自动提交事务,操作方法如下:

  1. 获取SqlSession对象:factory.openSession(true)
  2. 操作数据库,事务会自动提交
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//获取的是自动提交事务的SqlSession,所以不需要再手动关闭事务
SqlSession session = factory.openSession(true);
UserDao dao = session.getMapper(UserDao.class);

//操作数据库
dao.delete(48);

//释放资源,事务会自动提交,所以不需要再手动提交事务
session.close();
is.close();

将来Mybatis和Spring整合后,会由Spring来进行事务管理。

所以Mybatis的事务管理,了解即可

七、实现类方式CURD【拓展了解】

​ Mybatis中提供了两种dao层的开发方式:一是使用映射器接口代理对象的方式;二是使用映射器接口实现类的方式。其中代理对象的方式是主流,也是我们主要学习的内容。

1. 相关类介绍

1.1 SqlSession

1.1.1 SqlSession简介

​ SqlSession是一个面向用户的接口,定义了操作数据库的方法,例如:selectList, selectOne等等。

​ 每个线程都应该有自己的SqlSession对象,它不能共享使用,也是线程不安全的。因此最佳的使用范围是在请求范围内、或者方法范围内,绝不能将SqlSession放到静态属性中。

​ SqlSession使用原则:要做到SqlSession:随用随取,用完就关,一定要关

1.1.2 SqlSession的常用API

​ SqlSession操作数据库的常用方法有:

方法 作用
selectList(String statement, Object param) 查询多条数据,封装JavaBean集合
selectOne(String statement, Object param) 查询一条数据,封装JavaBean对象
查询一个数据,比如查询数量
insert(String statement, Object param) 添加数据,返回影响行数
update(String statement, Object param) 修改数据,返回影响行数
delete(String statement, Object param) 删除数据,返回影响行数
  • 以上方法中的参数statment,是映射文件中的namespace 和 id的值方法名组成的。

  • 例如:

    映射文件的namespace值为com.guang.dao.UserDao,执行的方法名是queryAll

    那么statement的值就是:com.guang.dao.UserDao.queryAll

1.2 SqlSessionFactory

​ 是一个接口,定义了不同的openSession()方法的重载。SqlSessionFactory一旦创建后,可以重复使用,通常是以单例模式管理。

​ SqlSessionFactory使用原则:单例模式管理,一个应用中,只要有一个SqlSessionFactory对象即可。

1.3 SqlSessionFactoryBuilder

​ 用于构建SqlSessionFactory工厂对象的。一旦工厂对象构建完成,就不再需要SqlSessionFactoryBuilder了,通常是作为工具类使用。

​ SqlSessionFactoryBuilder:只要生产了工厂,builder对象就可以垃圾回收了

2. 需求说明

针对user表进行CURD操作,要求使用映射器接口实现类的方式实现:

  • 查询全部用户,得到List<User>
  • 保存用户(新增用户)
  • 修改用户
  • 删除用户
  • 根据主键查询一个用户,得到User
  • 模糊查询
  • 查询数量

3. 示例代码

3.1 创建Java项目,准备JavaBean

1) 创建Java项目,导入依赖
2) 创建JavaBean
public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    //get/set...
    //toString...
}

3.2 准备Mybatis的映射器和配置文件

1) 创建映射器接口UserDao(暂时不需要增加方法,备用)
public interface UserDao{
    List<User> queryAll();
    void save(User user);
    void edit(User user);
    void delete(Integer id);
}
2) 创建映射器接口的实现类UserDaoImpl
public class UserDaoImpl implements UserDao{
    private SqlSessionFactory factory;

    /**
     * 构造方法。因为工厂对象,是整个应用只要一个就足够了,所以这里不要创建SqlSessionFactory对象
     * 而是接收获取到工厂对象来使用。
     */
    public UserDaoImpl(SqlSessionFactory factory) {
        this.factory = factory;
    }
    
    @Override
    public List<User> queryAll() {
        SqlSession session = factory.openSession();
        List<User> users = session.selectList("com.guang.dao.UserDao.queryAll");
        session.close();
        return users;
    }
    
    @Override
    public void save(User user) {
        SqlSession session = factory.openSession();
        session.insert("com.guang.dao.UserDao.save", user);
        session.commit();
        session.close();
    }
    
    @Override
    public void edit(User user) {
        SqlSession session = factory.openSession();
        session.update("com.guang.dao.UserDao.edit", user);
        session.commit();
        session.close();
    }
    
    @Override
    public void delete(Integer id) {
        SqlSession session = factory.openSession();
        session.delete("com.guang.dao.UserDao.delete", id);
        session.commit();
        session.close();
    }
}
3) 创建映射文件UserDao.xml(暂时不需要配置statement,备用)
<?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.guang.dao.UserDao">
    <select id="queryAll" resultType="User">
        select * from user
    </select>
    
    <insert id="save" parameterType="User">
        <selectKey resultType="int" keyProperty="id" order="AFTER">
            select last_insert_id()
        </selectKey>
        insert into user (id, username, birthday, address, sex) 
        values (#{id}, #{username}, #{birthday},#{address},#{sex})
    </insert>
    
    <update id="edit" parameterType="User">
        update user set username = #{username}, birthday = #{birthday}, 
        address = #{address}, sex = #{sex} where id = #{id}
    </update>
    
    <delete id="delete" parameterType="int">
        delete from user where id = #{uid}
    </delete>
</mapper>
4) 创建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>
    <typeAliases>
        <package name="com.guang.pojo"/>
    </typeAliases>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="com/guang/dao/UserDao.xml"/>
    </mappers>
</configuration>
4) 准备log4j.properties日志配置文件
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE            debug   info   warn error fatal
log4j.rootCategory=debug, CONSOLE

# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

3.3 准备单元测试类

public class MybatisDaoCURDTest {
    private InputStream is;
    private SqlSessionFactory factory;
    private UserDao dao;
    
    @Test
    public void testQueryAll(){
        List<User> users = dao.queryAll();
        for (User user : users) {
            System.out.println(user);
        }
    }
    
    @Test
    public void testSaveUser(){
        User user = new User();
        user.setUsername("tom");
        user.setAddress("广东深圳");
        user.setBirthday(new Date());
        user.setSex("男");

        System.out.println("保存之前:" + user);
        dao.save(user);
        System.out.println("保存之后:" + user);
    }
    
    @Test
    public void testEditUser(){
        User user = new User();
        user.setId(71);
        user.setUsername("jerry");
        user.setAddress("广东深圳宝安");
        user.setSex("女");
        user.setBirthday(new Date());

        dao.edit(user);
    }
    
    @Test
    public void testDeleteUser(){
        dao.delete(71);
    }

    @Before
    public void init() throws IOException {
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(is);
        //创建Dao实现类对象时,把factory作为构造参数传递进去---一个应用只要有一个factory就够了
        dao = new UserDaoImpl(factory);
    }

    @After
    public void destory() throws IOException {
        is.close();
    }
}

总结

  • 搭建Mybatis的开发环境

    • 在dao包里创建映射器接口,在接口里写方法
    • 创建映射文件,里边写方法对应的sql语句
      • 映射文件名称,必须和 映射器名称相同
      • 映射文件位置,必须和 映射器位置相同
    • 创建全局配置文件
      • 配置数据库环境
      • 配置所有映射器的位置
  • 如果要使用Mybatis实现一个功能,只要两步:

    1. 在映射器里增加方法
    2. 在映射文件里增加配置
  • 映射文件里的内容:

    • select标签:配置查询

      • id:对应的方法名称
      • parameterType:方法参数的类型,可以省略
      • resultType:查询结果集封装的类型
    • insert:配置插入

      • id:对应的方法名称
      • parameterType:方法参数的类型,可以省略
      • 如果要插入时获取最新的主键值
      <insert>
      	<selectKey keyProperty="id" resultType="int" order="AFTER">
          	select last_insert_id()
          </selectKey>
      </insert>
      
    • update:配置修改

      • id:对应的方法名称
      • parameterType:方法参数的类型,可以省略
    • delete:配置删除

      • id:对应的方法名称
      • parameterType:方法参数的类型,可以省略
  • parameterType:

    • 如果一个参数:
      • 如果是简单类型,#{随意写}
      • 如果是POJO,#{POJO的属性名}
      • 如果是Map,#{map的key}
      • 如果是List,#{list[索引]}
    • 如果多个参数:
      • 默认情况下:
        • #{arg0}, #{arg1}, ...
        • #{param1}, #{param2},...
      • 使用注解@Param指定参数名称
        • #{参数名称}
        • #{param1}, #{param2}, ...

注意使用开发规范!!!!!!!!!!!!

使用foreach标签的时候要注意!!!!!!!

mybatis将自动提交设置为关闭状态!!!!上面有分析

推荐阅读