mybatis

1.第一个mybatis程序

现在来创建我们的第一个mybatis程序,先练下手。

  1. 首先创建一个maven项目,在pom.xml里导入我们的项目环境

    • 这里说明一下:mysql连接包用5.1.47,不用8.0版本,那个设置有点区别,下面项目都用5.1.47版本
    • Maven静态资源过滤问题,所以要配置最下面的那个build标签
    • 内置jdk版本太低,会报错说不适合版本5,所以我是用properties那个标签来解决
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
    </dependencies>

    <groupId>org.example</groupId>
    <artifactId>mybatis-study</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>mybatis-01</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>13</java.version>
        <maven.compiler.source>13</maven.compiler.source>
        <maven.compiler.target>13</maven.compiler.target>
    </properties>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

</project>
  1. 编写mybatis配置文件:mybatis-config.xml

    • 这里面涉及到数据库连接,不同人有不同的密码和账号,且要注意连接的是哪一个数据库
    • 最下方有一个mapper标签,这个特别注意,叫映射器,下文会讲到
<?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">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="1234"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/chy/dao/UserMapper.xml"/>
    </mappers>
</configuration>
  1. 准备数据:新建数据库和实体类,这一步就不多说什么了

  2. 编写mybatis工具类
    注意:这里的resource名字就是第二步建立的文件名字

public class MybatisUtill {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取SqlSession连接
    public static SqlSession getSession(){
        return sqlSessionFactory.openSession();
    }
}
  1. 编写dao层接口,注意命名应为XXXMapper,编写配置文件,这个就是上面所说的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="com.chy.dao.UserMapper">

    <select id="getUserList" resultType="com.chy.bean.User">
    select * from user
    </select>
</mapper>
  1. 新建测试类进行测试
public class myTest {
    @Test
    public void test1(){
        SqlSession sqlSession = MybatisUtill.getSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }
}
  1. 这个项目如下,这样也就完成第一个mabatis的编写
    在这里插入图片描述

2.mybatis简述

mybatis是什么?mybatis是支持普通SQL查询、存储过程和高级映射的优秀持久层框架。它消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索,使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plain Ordinary Java Objects,普通的Java对象)映射成数据库中的记录。

总结一下,就是用来替代web项目中dao层和jdbc连接数据库的一个框架。下面讲一下如何实现它的功能,以上面我们建立的mybatis为例:

  • mybatis-config.xml+MybatisUtill工具类替代JDBC连接数据库
  • UserMapper.xml来替代dao层中接口的实现类

3.mybatis使用

1.简单的CURD

上面我们讲到了,mybatis是使用一个xml文件来代替dao层接口的实现类,dao层接口的实现类一般也是用来做curd的操作,那xml文件该如何写呢?下面来讲一下这个xml文件该如何写:

首先是namespace属性,这是一个很重要的属性,必须写对,否则会报错。它的值就是你要对应的dao层里的接口的全类名。

<?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.chy.dao.UserMapper">
	.....
</mapper>

这个xml文件里还有许多标签,比如代表CURD的四个标签,我们要采用什么操作,就使用哪一个标签。对于每一个标签的编写,其实就是在实现DAO层接口里的一个方法。

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chy.dao.UserMapper">
    <update id=""></update>
    <select id=""></select>
    <delete id=""></delete>
    <insert id=""></insert>
</mapper>

每一个标签里面也都有许多属性,这里先认识三个最基础的:

  • id:对应的方法名
  • parameterType:参数值类型
  • resultType:返回值类型,如果是对象要写全类名
public interface UserMapper {
   //根据id查询用户
   User selectUserById(int id);
}

<select id="selectUserById" resultType="com.kuang.pojo.User" parameterType="_int">
select * from user where id = #{id}
</select>

@Test
public void tsetSelectUserById() {
   SqlSession session = MybatisUtils.getSession();  //获取SqlSession连接
   UserMapper mapper = session.getMapper(UserMapper.class);
   User user = mapper.selectUserById(1);
   System.out.println(user);
   session.close();
}

这就是简单的CRUD,但是我们在实际使用的时候还会遇到许多问题,下面针对这些问题具体讲一下:

1.参数问题

上面我们已经知道可以用parameterType这个属性表示参数类型,但其实这个只能表示只有一个参数的时候,其实如果只有一个参数,不写这个属性也没什么问题。如下:

<select id="selectUserById" resultType="com.kuang.pojo.User">
select * from user where id = #{id}
</select>

那如果这么说的话,这个属性有什么用呢?要传多个参数的时候怎么办呢?解决方法有两个:利用parameterType这个属性+map 或者 使用注解。

  • 注解:

    1. 在接口方法的参数前加 @Param属性
    2. Sql语句编写的时候,直接取@Param中设置的值即可,不需要单独设置参数类型
    3. 注意:sql语句里的值是注解里的值,而不是参数的值
    //接口
    User selectUserByNP(@Param("username") String username,@Param("pwd")String password);
    
    //xml文件
    <select id="selectUserByNP" resultType="com.chy.bean.User">
     select * from user where name = #{username} and pwd = #{pwd}
    </select>
    
  • map + parameterType

    1. 在接口方法中,参数直接传递Map;
    2. 编写sql语句的时候,需要传递参数类型,参数类型为map
    3. 在使用方法的时候,Map的 key 为 sql中取的值即可,没有顺序要求
    //接口
    User selectUserByNP2(Map<String,Object> map);
    
    //xml文件
    <select id="selectUserByNP2" parameterType="map" resultType="com.chy.bean.User">
    select * from user where name = #{username} and pwd = #{pwd}
    </select>
    
    //测试类
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("username","小明");
    map.put("pwd","123456");
    User user = mapper.selectUserByNP2(map);
    

这里还有一个点需要注意一下:如果传入的参数是一个实体类对象,sql语句里需要的参数是实体类对象里的属性,它会自动匹配。#{值}就是去实体类里面找对应的属性。

int addUser(User user);

<insert id="addUser" parameterType="com.kuang.pojo.User">
    insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>

@Test
public void testAddUser() {
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   User user = new User(5,"王五","zxcvbn");
   int i = mapper.addUser(user);
   System.out.println(i);
   session.commit(); //提交事务,重点!不写的话不会提交到数据库
   session.close();
}

#{值}里的值必须和实体类的属性匹配,否则会报错。当修改的时候mybatis会帮我们去实体类里找对应的属性传入,但使用查询的时候,它也会帮我们找对应属性封装好。

User select(int id);

<select id="selectUserById" resultType="com.kuang.pojo.User">
select * from user where id = #{id}
</select>

@Test
public void tsetSelectUserById() {
   SqlSession session = MybatisUtils.getSession();  //获取SqlSession连接
   UserMapper mapper = session.getMapper(UserMapper.class);
   User user = mapper.selectUserById(1);
   System.out.println(user);
   session.close();
}

这里它会帮我们查到的东西自动帮我们封装到User里面去,但是这里也有一个问题,就是数据库一般都是采用驼峰命名和实体类的命名一般是不同的,假如是这样的话就有问题,它就会找不到对应的属性,所以值为空,下面会细讲这个问题。

2.事务问题

除了select查询数据外,update、delete、insert都是写数据,都是事务所以必须提交事务,数据才会改变,除非你设置了自动提交事务。

注意:update、delete、insert是没有返回值的,也就是说没有resultType这个属性。

int addUser(User user);

<insert id="addUser" parameterType="com.kuang.pojo.User">
    insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>

@Test
public void testAddUser() {
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   User user = new User(5,"王五","zxcvbn");
   int i = mapper.addUser(user);
   System.out.println(i);
   session.commit(); //提交事务,重点!不写的话不会提交到数据库
   session.close();
}

总结一下:

  • 所有的增删改操作都需要提交事务!

  • 接口所有的普通参数,尽量都写上@Param参数,尤其是多个参数时,必须写上!

  • 有时候根据业务的需求,可以考虑使用map传递参数!

  • 为了规范操作,在SQL的配置文件中,我们尽量将Parameter参数和resultType都写上!

当然,我们也可以使用自动提交事务的功能,那怎么开启呢?z

2.复杂的CRUD

1.模糊查询

第1种:在Java代码中添加sql通配符

string wildcardname = “%smi%”;
list<name> names = mapper.selectlike(wildcardname);

<select id=”selectlike”>
select * from foo where bar like #{value}
</select>

第2种:在sql语句中拼接通配符,会引起sql注入

string wildcardname = “smi”;
list<name> names = mapper.selectlike(wildcardname);

<select id=”selectlike”>
    select * from foo where bar like "%"#{value}"%"
</select

注意,这里有一个面试点:#{}${} 有什么区别,简单了解一下第一个不会有sql注入问题,第二个有sql注入问题。

2.分页查询

分页查询有两种方式,一种是是使用sql语句,一种是使用RowBounds类:

  • sql语句
    1、修改Mapper文件

    <select id="selectUser" parameterType="map" resultType="user">
      select * from user limit #{startIndex},#{pageSize}
    </select>
    

    2、Mapper接口,参数为map

    //选择全部用户实现分页
    List<User> selectUser(Map<String,Integer> map);
    

    3、在测试类中传入参数测试

    推断:起始位置 = (当前页面 - 1 ) * 页面大小

    //分页查询 , 两个参数startIndex , pageSize
    @Test
    public void testSelectUser() {
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
    
       int currentPage = 1;  //第几页
       int pageSize = 2;  //每页显示几个
       Map<String,Integer> map = new HashMap<String,Integer>();
       map.put("startIndex",(currentPage-1)*pageSize);
       map.put("pageSize",pageSize);
    
       List<User> users = mapper.selectUser(map);
    
       for (User user: users){
           System.out.println(user);
      }
    
       session.close();
    }
    
  • rowbounds类
    1、mapper接口

    //选择全部用户RowBounds实现分页
    List<User> getUserByRowBounds();
    

    2、mapper文件

    <select id="getUserByRowBounds" resultType="user">
    select * from user
    </select>
    

    3、测试类

    在这里,我们需要使用RowBounds类

    @Test
    public void testUserByRowBounds() {
       SqlSession session = MybatisUtils.getSession();
    
       int currentPage = 2;  //第几页
       int pageSize = 2;  //每页显示几个
       RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);
    
       //通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
       List<User> users = session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
    
       for (User user: users){
           System.out.println(user);
      }
       session.close();
    }
    

第二种方式其实已经过期,使用第一种方式即可,其实也还有其他方式实现分页,比如使用插件。

3.实体类里面有javaBean(多对一)

环境准备:

CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');

CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8


INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

在这里插入图片描述
要求:查询学生信息以及他们对应的老师信息

  1. 创建bean对象

    @Data //GET,SET,ToString,有参,无参构造
    public class Teacher {
       private int id;
       private String name;
    }
    
    @Data
    public class Student {
       private int id;
       private String name;
       //多个学生可以是同一个老师,即多对一
       private Teacher teacher;
    }
    
  2. 编写接口方法

    public interface StudentMapper {
    	public List<Student> getStudents();
    }
    public interface TeacherMapper {
    	public Teacher getTeacher(int id);
    }
    
  3. 写mapper.xml文件

    • 按结果嵌套
    <select id="getStudents2" resultMap="StudentTeacher2" >
      select s.id sid, s.name sname , t.name tname
      from student s,teacher t
      where s.tid = t.id
    </select>
    
    <resultMap id="StudentTeacher2" type="Student">
       <id property="id" column="sid"/>
       <result property="name" column="sname"/>
       <!--关联对象property 关联对象在Student实体类中的属性-->
       <association property="teacher" javaType="Teacher">
           <result property="name" column="tname"/>
       </association>
    </resultMap>
    
    1. 按查询嵌套
    <select id="getStudents" resultMap="StudentTeacher">
    select * from student
    </select>
    <resultMap id="StudentTeacher" type="Student">
        <association property="teacher"  column="tid" javaType="Teacher" select="getTeacher"/>
     </resultMap>
       
    <select id="getTeacher" resultType="teacher">
       select * from teacher where id = #{id}
    </select>
    
  • 按照查询进行嵌套处理就像SQL中的子查询
  • 按照结果进行嵌套处理就像SQL中的联表查询
4.实体类里面有javaBean(一对多)

环境还是上面的环境,不过需求变了:查询一个老师的信息,以及还有他的所有学生。

  • 实体类

    @Data 
    public class Teacher {
       private int id;
       private String name;
       //一个老师多个学生
       private List<Student> students;
    }
    
  • 按结果嵌套

<mapper namespace="com.kuang.mapper.TeacherMapper">
   <select id="getTeacher" resultMap="TeacherStudent">
      select s.id sid, s.name sname , t.name tname, t.id tid
      from student s,teacher t
      where s.tid = t.id and t.id=#{id}
   </select>

   <resultMap id="TeacherStudent" type="Teacher">
       <result  property="name" column="tname"/>
       <collection property="students" ofType="Student">
           <result property="id" column="sid" />
           <result property="name" column="sname" />
           <result property="tid" column="tid" />
       </collection>
   </resultMap>
</mapper>
  • 按查询嵌套
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{id}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
   <!--column是一对多的外键 , 写的是一的主键的列名-->
   <collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
  select * from student where tid = #{id}
</select>
5.动态sql
  • if语句

    需求:如果标题不为空则把标题做条件,如果作者不为空,则把作者做条件
    <select id="queryBlogIf" parameterType="map" resultType="blog">
      select * from blog where 1 = 1
       <if test="title != null">
        and title = #{title}
       </if>
       <if test="author != null">
          and author = #{author}
       </if>
    </select>
    
  • where语句

    需求:去掉了1=1这句废话,“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
    <select id="queryBlogIf" parameterType="map" resultType="blog">
      select * from blog
      <where>
       <if test="title != null">
          title = #{title}
       </if>
       <if test="author != null">
          and author = #{author}
       </if>
       </where>
    </select>
    
  • set语句

    需求:作用类似于where,会自动剔除掉一些东西,注意set是用的逗号隔开,set会剔除最后一个属性的逗号
    <update id="updateBlog" parameterType="map">
      update blog
         <set>
             <if test="title != null">
                title = #{title},
             </if>
             <if test="author != null">
                author = #{author},
             </if>
         </set>
      where id = #{id};
    </update>
    
  • choose语句

    需求:类似于switch语句的意思
    <select id="queryBlogChoose" parameterType="map" resultType="blog">
      select * from blog
       <where>
           <choose>
               <when test="title != null">
                    title = #{title}
               </when>
               <when test="author != null">
                  and author = #{author}
               </when>
               <otherwise>
                  and views = #{views}
               </otherwise>
           </choose>
       </where>
    </select>
    
  • SQL片段
    有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

    <sql id="if-title-author">
       <if test="title != null">
          title = #{title}
       </if>
       <if test="author != null">
          and author = #{author}
       </if>
    </sql>
    
    <select id="queryBlogIf" parameterType="map" resultType="blog">
      select * from blog
       <where>
           <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
           <include refid="if-title-author"></include>
           <!-- 在这里还可以引用其他的 sql 片段 -->
       </where>
    </select>
    
    • 最好基于 单表来定义 sql 片段,提高片段的可重用性
    • 在 sql 片段中不要包括 where
  • Foreach

    <select id="queryBlogForeach" parameterType="map" resultType="blog">
      select * from blog
       <where>
           <!--
           collection:指定输入对象中的集合属性
           item:每次遍历生成的对象
           open:开始遍历时的拼接字符串
           close:结束时拼接的字符串
           separator:遍历对象之间需要拼接的字符串
           select * from blog where 1=1 and (id=1 or id=2 or id=3)
         -->
           <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
              id=#{id}
           </foreach>
       </where>
    </select>
    

3.配置解析

我们已经了解到mybatis里有一个核心配置文件mybatis-config.xml,也可以不叫这个名但这个也是为了规范,我们通过这个来连接了数据库,充当jdbc的作用,但里面还有许多属性,用来设置mybatis。

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息,大概有以下这么些属性:

configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

注意元素节点的顺序!顺序不对会报错,比如mappers属性就必须写在最后面,否则会出错。

1.environment
<environments default="development">
 <environment id="development">
   <transactionManager type="JDBC">
     <property name="..." value="..."/>
   </transactionManager>
   <dataSource type="POOLED">
     <property name="driver" value="${driver}"/>
     <property name="url" value="${url}"/>
     <property name="username" value="${username}"/>
     <property name="password" value="${password}"/>
   </dataSource>
 </environment>
</environments>

配置MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定environment的id),id便是每一个environment的名字。

  • 子元素节点:environment

    • dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源,数据源是必须配置的。有三种内建的数据源类型:

      1. unpooled:这个数据源的实现只是每次被请求时打开和关闭连接。

      2. pooled:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得并发 Web 应用快速响应请求的流行处理方式。

      3. jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

      数据源也有很多第三方的实现,比如dbcp,c3p0,druid等等…详情点击查看官方文档。

    • transactionManager :事务管理器,有两种JDBC 和 MANAGED,但后一种已经被淘汰了,只需使用JDBC即可。

2.mappers

映射器 ,定义映射SQL语句文件,既然 MyBatis 的行为其他元素已经配置完了,我们现在就要定义 SQL 映射语句了。

但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。

你可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。映射器是MyBatis中最核心的组件之一,在MyBatis 3之前,只支持xml映射器,即:所有的SQL语句都必须在xml文件中配置。

而从MyBatis 3开始,还支持接口映射器,这种映射器方式允许以Java代码的方式注解定义SQL语句,非常简洁。

使用mapper标签引入资源有三种方式,之前我们使用的都只是其中一种:

  • resource:使用相对于类路径的资源引用,可以在任何位置只要地址写对即可

    <!-- 使用相对于类路径的资源引用 -->
    <mappers>
     <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    
  • url:使用完全限定资源定位符

    <!-- 使用完全限定资源定位符(URL) -->
    <mappers>
     <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    </mappers>
    
  • class:使用映射器接口实现类的完全限定类名,需要配置文件名称和接口名称一致,并且位于同一目录下

    <!--使用映射器接口实现类的完全限定类名需要配置文件名称和接口名称一致,并且位于同一目录下-->
    <mappers>
     <mapper class="org.mybatis.builder.AuthorMapper"/>
    </mappers>
    
  • packet:将包内的映射器接口实现全部注册为映射器但是需要配置文件名称和接口名称一致,并且位于同一目录下

    <!--将包内的映射器接口实现全部注册为映射器但是需要配置文件名称和接口名称一致,并且位于同一目录下-->
    <mappers>
    <package name="org.mybatis.builder"/>
    </mappers>
    
3.Properties

数据库这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。

第一步 :在资源目录下新建一个db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=1234

第二步 : 将文件导入properties 配置文件

<configuration>
   <!--导入properties文件-->
   <properties resource="db.properties"/>

   <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
           		//使用${name}到入值
               <property name="driver" value="${driver}"/>
               <property name="url" value="${url}"/>
               <property name="username" value="${username}"/>
               <property name="password" value="${password}"/>
           </dataSource>
       </environment>
   </environments>
   <mappers>
       <mapper resource="mapper/UserMapper.xml"/>
   </mappers>
</configuration>

这样就实现了配置文件和代码分离,不同用户连接不同数据库只要在db.properties里面修改自己的账号密码即可。

上面就演示了properties的用法,使用properties导入外部文件后,就可以使用${name}获得值。

4.typeAliases

上面我们在配置xml文件的时候,发现映射类的时候都要写全类名,很麻烦这里就可以使用typeAliases这个属性来简化我们的书写。

类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。

//配置文件
<typeAliases>
   <typeAlias type="com.chy.bean.User" alias="User"/>
</typeAliases>

//mapper文件
<select id="selectUserBynameBypwd" resultType="User">
    select * from user where name = #{name} and pwd = #{pwd}
</select>

<insert id="insertUser" parameterType="User">
    insert into user(id,name,pwd) values (#{id},#{name},#{pwd});
</insert>

当这样配置时,User可以用在任何使用com.chy.bean.User的地方。也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
   <package name="com.chy.bean"/>
</typeAliases>

每一个在包 com.chy.bean 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。

若有注解,则别名为其注解值。见下面的例子:

@Alias("user")
public class User {
  ...
}

总结一下,一共有三种方式可以用来起别名:

  • 使用typeAliases属性的typeAlias标签
  • 使用typeAliases属性的packet进行全包扫描
  • 使用@Alias注解

当然注意一点:mybatis有一些常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
在这里插入图片描述
在这里插入图片描述

5.settings

这个属性是用来设置mybatis一些设置的,用来开启mybatis的一些东西,有很多内容,一个完整的setting属性如下:

<settings>
 <setting name="cacheEnabled" value="true"/>
 <setting name="lazyLoadingEnabled" value="true"/>
 <setting name="multipleResultSetsEnabled" value="true"/>
 <setting name="useColumnLabel" value="true"/>
 <setting name="useGeneratedKeys" value="false"/>
 <setting name="autoMappingBehavior" value="PARTIAL"/>
 <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
 <setting name="defaultExecutorType" value="SIMPLE"/>
 <setting name="defaultStatementTimeout" value="25"/>
 <setting name="defaultFetchSize" value="100"/>
 <setting name="safeRowBoundsEnabled" value="false"/>
 <setting name="mapUnderscoreToCamelCase" value="false"/>
 <setting name="localCacheScope" value="SESSION"/>
 <setting name="jdbcTypeForNull" value="OTHER"/>
 <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

这些只是其中比较重要的功能,还有其他功能就不细讲了,对于这四个功能后面也会讲到,这里就简单提一下。

  • 懒加载
  • 日志实现
  • 缓存开启关闭
  • 驼峰命名转化

4.生命周期和作用域

这里对于mybatis的生命周期和作用域做一个简单的笔记,对于这个还没有很深入,以后再来深究。首先是mybatis的流程图:

在这里插入图片描述
关于sqlsession操作,有两种方式:一种通过mapper上文就是通过这种方式,另一种是直接接触我们的接口方法,现在一般都是用第一种,第二种已经淘汰了,这里也不深究了。

  • SqlSessionFactoryBuilder
    作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。

    因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

  • SqlSessionFactory
    可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中。

    所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。

    由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。

    因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例(单例模式),让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域。

  • SqlSession
    SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。

    所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。

    所以 SqlSession 的最佳的作用域是请求或方法作用域。
    在这里插入图片描述
    还有mybatis的详细过程:
    在这里插入图片描述

5.resultMap

上面我们讲到过一个问题:实体类的属性名和数据库里的字段名不一致。

  1. 查看之前的数据库的字段名

  2. Java中的实体类设计

       public class User {
       private int id;  //id
       private String name;   //姓名
       private String password;   //密码和数据库不一样!
       
       //构造
       //set/get
       //toString()
       }
    
  3. 接口

    //根据id查询用户
    User selectUserById(int id);
    
  4. mapper映射文件

    <select id="selectUserById" resultType="user">
     select * from user where id = #{id}
    </select>
    
  5. 测试

    @Test
    public void testSelectUserById() {
       SqlSession session = MybatisUtils.getSession();  //获取SqlSession连接
       UserMapper mapper = session.getMapper(UserMapper.class);
       User user = mapper.selectUserById(1);
       System.out.println(user);
       session.close();
    }
    

结果:User{id=1, name=‘小明’, password=‘null’},查询出来发现 password 为空 . 说明出现了问题。

分析一下:

select * from user where id = #{id} 可以看做

select  id,name,pwd  from user where id = #{id}

mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set方法设值 , 由于找不到setPwd() , 所以password返回null ; 【自动映射】

对于这种问题有两种解决方式:一种是使用别名,另一种是使用resultMap,也就是这一节的主角。

  • 方案一:为列名指定别名 , 别名和java实体类的属性名一致 ,不推荐使用这种方法太笨比了

    <select id="selectUserById" resultType="User">
      select id , name , pwd as password from user where id = #{id}
    </select>
    
  • 方案二:使用结果集映射->ResultMap

    <resultMap id="UserMap" type="User">
       <!-- id为主键 -->
       <id column="id" property="id"/>
       <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
       <result column="name" property="name"/>
       <result column="pwd" property="password"/>
    </resultMap>
    
    <select id="selectUserById" resultMap="UserMap">
      select id , name , pwd from user where id = #{id}
    </select>
    

上面就是利用了resultMap,下面来讲一下:

  • 自动映射

    • resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来。

    • 实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的长达数千行的代码。

    • ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。

你已经见过简单映射语句的示例了,但并没有显式指定 resultMap。比如:

<select id="selectUserById" resultType="map">
select id , name , pwd from user where id = #{id}
</select>

上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 不是一个很好的模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为模型。

ResultMap 最优秀的地方在于,虽然你已经对它相当了解了,但是根本就不需要显式地用到他们。

  • 手动映射

    1、返回值类型为resultMap

    <select id="selectUserById" resultMap="UserMap">
      select id , name , pwd from user where id = #{id}
    </select>
    

    2、编写resultMap,实现手动映射

    <resultMap id="UserMap" type="User">
       <!-- id为主键 -->
       <id column="id" property="id"/>
       <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
       <result column="name" property="name"/>
       <result column="pwd" property="password"/>  //对于解决上面的那个问题,除了这一行要写,其他都可以不写,因为其他属性与数据库中字段名是对应的,他也会自动去找实体类的
    </resultMap>
    

6.日志安装

mybatis里面是有一个日志工厂的,且mybatis是有很多个日志种类的,Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

  • SLF4J

  • Apache Commons Logging

  • Log4j 2

  • Log4j

  • JDK logging

具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。

这里选择Log4j来作为我的mybatis日志:

  • Log4j是Apache的一个开源项目

  • 通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件…

  • 我们也可以控制每一条日志的输出格式;

  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

使用步骤:

1、导入log4j的包
<dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.17</version>
</dependency>

2、配置文件编写
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG


3、setting设置日志实现
<settings>
   <setting name="logImpl" value="LOG4J"/>
</settings>

4、在程序中使用Log4j进行输出!
//注意导包:org.apache.log4j.Logger
static Logger logger = Logger.getLogger(MyTest.class);

@Test
public void selectUser() {
   logger.info("info:进入selectUser方法");
   logger.debug("debug:进入selectUser方法");
   logger.error("error: 进入selectUser方法");
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   List<User> users = mapper.selectUser();
   for (User user: users){
       System.out.println(user);
  }
   session.close();
}

7.使用注解开发

我们都知道,注解是框架的灵魂,但对于mybatis来说,我们更多的是使用xml配置文件来操作,因为它能实现更多更复杂的功能,但其实对于简单的CRUD来说,mybatis也能使用注解来实现。

  • @select ()
  • @update ()
  • @Insert ()
  • @delete ()

注意:利用注解开发就不需要mapper.xml映射文件了 .

1、我们在我们的接口中添加注解

//查询全部用户
@Select("select id,name,pwd password from user")
public List<User> getAllUser();

2、在mybatis的核心配置文件中注入

<!--使用class绑定接口-->
<mappers>
   <mapper class="com.kuang.mapper.UserMapper"/>
</mappers>

3、测试

@Test
public void testGetAllUser() {
   SqlSession session = MybatisUtils.getSession();
   //本质上利用了jvm的动态代理机制
   UserMapper mapper = session.getMapper(UserMapper.class);

   List<User> users = mapper.getAllUser();
   for (User user : users){
       System.out.println(user);
  }

   session.close();
}

本质上利用了jvm的动态代理机制,这里简单给下图,具体以后再深究:
在这里插入图片描述
使用注解确实很方便,但只能做一些比较简单的操作,比如遇到上面提到过的数据库字段和实体类属性不匹配的情况,就无法解决了。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值