0
点赞
收藏
分享

微信扫一扫

Mybatis框架详解【重点】

楠蛮鬼影 2022-03-12 阅读 89

1.1 Mybatis概述

随着互联网的发展,越来越多的公司摒弃了Hibernate,而选择拥抱了MyBatis。

Mybatis是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性

1.1.1 ORM

Object Relation Mapping,对象关系映射对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,比如用一个Java的Student类,去对应数据库中的一张student表,类中的属性和表中的列一一对应。Student类就对应student表,一个Student对象就对应student表中的一行数据。

  之所以说Mybatis是半自动ORM框架,是因为Mybatis进行开发时,需要手动编写SQL语句。而全自动的ORM框架,如Hibernate,则不需要编写SQL语句。用Hibernate开发,只需要定义好ORM映射关系,就可以直接进行CRUD操作了。由于Mybatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以Mybatis的数据库无关性低虽然Mybatis需要手写SQL,但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发的效率

1.2 入门小案例

只需要通过如下几个步骤,即可用Mybatis快速进行持久层的开发

  • 编写全局配置文件
  • 编写mapper映射文件
  • 加载全局配置文件,生成SqlSessionFactory
  • 创建SqlSession,调用mapper映射文件中的SQL语句来执行CRUD操作

1.2.1 建表

在本地虚拟机Mysql上创建一个库yogurt,并在里面创建一张student表

1.2.2 创建实体类

public class Student {

	private Integer id;
	private String name;
	private Integer score;
	private Integer age;
	private Integer gender;
}

1.2.3 编写mapper映射文件(编写SQL)

<!-- StudentMapper.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="test">
    <select id="findAll" resultType="com.yogurt.po.Student">
        SELECT * FROM student;
    </select>

    <insert id="insert" parameterType="com.yogurt.po.Student">
        INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
    </insert>
    
    <delete id="delete" parameterType="int">
        DELETE FROM student WHERE id = #{id};
    </delete>
</mapper>

1.2.4 编写数据源properties文件

db.url=jdbc:mysql://192.168.183.129:3306/yogurt?characterEncoding=utf8
db.user=root
db.password=root
db.driver=com.mysql.jdbc.Driver

1.2.5 编写全局配置文件(主要是配置数据源信息)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 配置文件信息 -->
    <properties resource="properties/db.properties"></properties>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 从配置文件中加载属性 -->
                <property name="driver" value="${db.driver}"/>
                <property name="url" value="${db.url}"/>
                <property name="username" value="${db.user}"/>
                <property name="password" value="${db.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- 加载前面编写的SQL语句的文件 -->
        <mapper resource="StudentMapper.xml"/>
    </mappers>

</configuration>

1.2.6 编写Dao层类

public class StudentDao {

	private SqlSessionFactory sqlSessionFactory;

	public StudentDao(String configPath) throws IOException {
		InputStream inputStream = Resources.getResourceAsStream(configPath);
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}

	public List<Student> findAll() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		List<Student> studentList = sqlSession.selectList("findAll");
		sqlSession.close();
		return studentList;
	}

	public int addStudent(Student student) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		int rowsAffected = sqlSession.insert("insert", student);
		sqlSession.commit();
		sqlSession.close();
		return rowsAffected;
	}

	public int deleteStudent(int id) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		int rowsAffected = sqlSession.delete("delete",id);
		sqlSession.commit();
		sqlSession.close();
		return rowsAffected;
	}
}

1.2.7 测试

public class SimpleTest {

	private StudentDao studentDao;

	@Before
	public void init() throws IOException {
		studentDao = new StudentDao("mybatis-config.xml");
	}

	@Test
	public void insertTest() {
		Student student = new Student();
		student.setName("yogurt");
		student.setAge(24);
		student.setGender(1);
		student.setScore(100);
		studentDao.addStudent(student);
	}

	@Test
	public void findAllTest() {
		List<Student> all = studentDao.findAll();
		all.forEach(System.out::println);
	}
}

在这里插入图片描述

1.2.8 入门案例总结

  • 编写mapper.xml,书写SQL,并定义好SQL的输入参数,和输出参数
  • 编写全局配置文件,配置数据源,以及要加载的mapper.xml文件
  • 通过全局配置文件,创建SqlSessionFactory
  • 每次进行CRUD时,通过SqlSessionFactory创建一个SqlSession
  • 调用SqlSession上的selectOne,selectList,insert,delete,update等方法,传入mapper.xml中SQL标签的id,以及输入参数

1.3 增删改查

1.3.1 namespace

namespace中的包名要和Dao/mapper接口的包名保持一致

1.3.2 select

package com.rui.dao;

public interface UserMapper {
    //根据id查询用户
    User getUserById(int id);
}

编写对应的mapper中的sql语句

 <select id="getUserById" resultType="com.rui.pojo.User" parameterType="int">
       select * from mybatis.user where id = #{id};
   </select>

测试

    @Test
   public void getUserById(){
       SqlSession sqlSession = MyBatisUtils.getSqlSession();
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
       User user = mapper.getUserById(1);
       System.out.println(user);
       sqlSession.close();
   }

1.4.3 3 Insert

1.4.3.4 Update

1.4.3.5 Delete

<?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核心配置文件-->
<configuration>
    <!--environments配置环境组-->
    <!--default默认环境-->
    <environments default="development">
        <!--environment单个环境-->
        <environment id="development">
            <!--transactionManager配置事务管理器-->
            <transactionManager type="JDBC"/>
            <!--配置连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useSSL=true&amp;useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
 
    <!--每一个Mapper.xml需要在Mybatis核心配置文件中注册-->
    <mappers>
        <mapper resource="com/newer/dao/UserMapper.xml"/>
    </mappers>
</configuration>

package com.newer.utils;
public class MyBatisUtils {
 
    private static SqlSessionFactory sqlSessionFactory;
 
    static {
        try {
            //使用mybatis第一步、获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    //既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例了。
    // SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。
    public static SqlSession getSqlSession(){
      return sqlSessionFactory.openSession();
    }
}

package com.newer.pojo;
 
//实体类
public class User {
    private int id;
    private String name;
    private String pwd; 
}

package com.newer.dao;

public interface UserMapper {
 
    //查询全部用户
    List<User> getUserList();
 
    //根据ID查询用户
    User getUserById(int id);
 
    //insert一个用户
    int addUser(User user);
 
    //修改用户
    int updateUser(User user);
 
    //删除用户
    int deleteUser(int 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">
 
<!--namespace绑定一个对应的Mapper接口-->
<mapper namespace="com.newer.dao.UserMapper">
   <select id="getUserList" resultType="com.newer.pojo.User">
       select * from mybatis.user
   </select>
 
    <select id="getUserById" parameterType="int" resultType="com.newer.pojo.User">
        select * from mybatis.user where id=#{id}
    </select>
 
    <insert id="addUser" parameterType="com.newer.pojo.User">
        insert into mybatis.user (id,name,pwd) values (#{id},#{name },#{pwd})
    </insert>
 
    <update id="updateUser" parameterType="com.newer.pojo.User">
        update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
    </update>
 
    <delete id="deleteUser" parameterType="int">
        delete  from mybatis.user where id=#{id}
    </delete>
</mapper>

package com.newer.dao;
 
public class UserDaoTest {
    @Test
    public void test(){
        //第一步:获得SqlSession对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
 
        //方式一:getMapper
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userDao.getUserList();
 
        //方式二
        //List<User> userList = sqlSession.selectList("com.newer.dao.UserMapper.getUserList");
     
        for (User user : userList) {
            System.out.println(user);
        }
        //关闭sqlSession
        sqlSession.close();
    }
 
    @Test
    public void getUserById(){
        //第一步:获得SqlSession对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1);
        System.out.println(user);
        //关闭sqlSession
        sqlSession.close();
    }
 
    //增删改需要提交事务
    @Test
    public void addUser(){
        //第一步:获得SqlSession对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int res = mapper.addUser(new User(4, "哈哈", "12364"));
        if(res>0){
            System.out.println("插入成功!");
        }
        //提交事务
        sqlSession.commit();
        //关闭sqlSession
        sqlSession.close();
    }
 
    @Test
    public void updateUser(){
        //第一步:获得SqlSession对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int res = mapper.updateUser(new User(4,"呵呵","88888"));
        if(res>0){
            System.out.println("修改成功!");
        }
        //提交事务
        sqlSession.commit();
        //关闭sqlSession
        sqlSession.close();
    }
 
    @Test
    public void deleteUser(){
        //第一步:获得SqlSession对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int res = mapper.deleteUser(4);
        if(res>0){
            System.out.println("删除成功!");
        }
        //提交事务
        sqlSession.commit();
        //关闭sqlSession
        sqlSession.close();
    }
}

注意点:增删改需要提交事务

1.4.3.6 万能Map

我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用Map

int addUser2(Map<String,Object> map);

<!--对象中的属性,可以直接取出来 parameterType=传递map中的key-->
<insert id="addUser2" parameterType="map">
    insert into mybatis.user (id, name, pwd) values (#{userId},#{userName},#{password});
</insert>

@Test
public void addUser2(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put(“userId”,4);
map.put(“userName”,“王五”);
map.put(“password”,23333);
mapper.addUser2(map);
//提交事务
sqlSession.commit();
sqlSession.close();
}

注意

  • Map传递参数,直接在sql中取出key即可【parameterType=“map”】
  • 对象传递参数,直接在sql中取对象的属性即可【parameterType=“Object”】
  • 只有一个基本类型参数的情况下,可以直接在sql中取到
  • 多个参数用Map,或者注解

1.5 Mybatis配置文件详解

1.5.1 标签详解

全局配置文件中,各个标签要按照如下顺序进行配置,因为Mybatis加载配置文件的源码中是按照这个顺序进行解析的

<configuration><!--配置--!>

     properties  <!--属性--!>
     settings<!--配置--!>
     typeAliases<!--类型别名--!>
     typeHandlers<!--类型处理器--!>
     objectFactory<!--对象工厂--!>
     plugins<!--插件--!>
     environments<!--环境配置--!>
        environment<!--环境变量--!>
            transactionManager<!--事务管理器--!>
            dataSource<!--数据源--!>
     mappers<!--映射器--!>

</configuration>

各个子标签说明如下

  • <properties>
    一般将数据源的信息单独放在一个properties文件中,然后用这个标签引入,在下面environment标签中,就可以用${}占位符快速获取数据源的信息。

  • <settings>
    用来开启或关闭mybatis的一些特性,比如可以用<setting name="lazyLoadingEnabled" value="true"/>来开启延迟加载,可以用<settings name="cacheEnabled" value="true"/>来开启二级缓存

  • <typeAliases>
    在mapper.xml中需要使用parameterType和resultType属性来配置SQL语句的输入参数类型和输出参数类型,类必须要写上全限定名,比如一个SQL的返回值映射为Student类,则resultType属性要写com.yogurt.po.Student,这太长了,所以可以用别名来简化书写,比如

    <typeAliases>
        <typeAlias type="com.yogurt.po.Student" alias="student"/>
    </typeAliases>
    

    之后就可以在resultType上直接写student,Mybatis会根据别名配置自动找到对应的类。
    当然,如果想要一次性给某个包下的所有类设置别名,可以用如下的方式

    		<typeAliases>
    		   <package name="com.yogurt.po"/>
    		</typeAliases>
    

    如此,指定包下的所有类,都会以简单类名的小写形式,作为它的别名

    另外,对于基本的Java类型 — 8大基本类型以及包装类,以及String类型,Mybatis提供了默认的别名,别名为其简单类名的小写,比如原本需要写java.lang.String,其实可以简写为string

  • <typeHandlers>
    用于处理Java类型和Jdbc类型之间的转换,mybatis有许多内置的TypeHandler,比如StringTypeHandler,会处理Java类型String和Jdbc类型CHAR和VARCHAR。这个标签用的不多

  • <objectFactory>
    mybatis会根据resultType或resultMap的属性来将查询得到的结果封装成对应的Java类,它有一个默认的DefaultObjectFactory,用于创建对象实例,这个标签用的也不多

  • <plugins>
    可以用来配置mybatis的插件,比如在开发中经常需要对查询结果进行分页,就需要用到pageHelper分页插件,这些插件就是通过这个标签进行配置的。在Mybatis底层,运用了责任链模式+动态代理去实现插件的功能

    <!-- PageHelper 分页插件 -->
    <plugins>
      <plugin interceptor="com.github.pagehelper.PageInterceptor">
         <property name="helperDialect" value="mysql"/>
      </plugin>
    </plugins>
    
  • <environments>
    用来配置数据源

  • <mappers>
    用来配置mapper.xml映射文件,这些xml文件里都是SQL语句

1.5.2 SQL语句中的占位符${}和#{}

一般会采用#{},#{}在mybatis中,最后会被解析为?,其实就是Jdbc的PreparedStatement中的?占位符,它有预编译的过程,会对输入参数进行类型解析(如果入参是String类型,设置参数时会自动加上引号),可以防止SQL注入

如果parameterType属性指定的入参类型是简单类型的话(简单类型指的是8种java原始类型再加一个String),#{}中的变量名可以任意,如果入参类型是pojo,比如是Student类

public class Student{
	private String name;
    private Integer age;
    //setter/getter
}

那么#{name}表示取入参对象Student中的name属性,#{age}表示取age属性,这个过程是通过反射来做的

而${},一般会用在模糊查询的情景,比如SELECT * FROM student WHERE name like '%${name}%';

它的处理阶段在#{}之前,它不会做参数类型解析,而仅仅是做了字符串的拼接,若入参的Student对象的name属性为zhangsan,则上面那条SQL最终被解析为SELECT * FROM student WHERE name like '%zhangsan%';

而如果此时用的是SELECT * FROM student WHERE name like '%#{name}%'; 这条SQL最终就会变成SELECT * FROM student WHERE name like '%'zhangsan'%';

所以模糊查询只能用${},虽然普通的入参也可以用${},但由于${}不会做类型解析,就存在SQL注入的风险,比如
SELECT * FROM user WHERE name = '${name}' AND password = '${password}'
我可以让一个user对象的password属性为’OR ‘1’ = '1,最终的SQL就变成了
SELECT * FROM user WHERE name = 'yogurt' AND password = ''OR '1' = '1',因为OR ‘1’ = '1’恒成立,这样攻击者在不需要知道用户名和密码的情况下,也能够完成登录验证

另外,对于pojo的入参,${}中获取对象属性的语法和#{}几乎一样,但${}在Mybatis底层是通过OGNL表达式语言进行处理的,这跟#{}的反射处理有所不同

对于简单类型(8种java原始类型再加一个String)的入参,${}中参数的名字必须是value,例子如下

<select id="fuzzyCount" parameterType="string" resultType="int">
        SELECT count(1) FROM `user` WHERE name like '%${value}%'
</select>

为什么简单类型的变量名必须为value呢?因为Mybatis源码中写死的value,哈哈

在这里插入图片描述
上面其实是比较原始的开发方式,我们需要编写dao类,针对mapper.xml中的每个SQL标签,做一次封装,SQL标签的id要以字符串的形式传递给SqlSession的相关方法,容易出错,非常不方便;为了简化开发,mybatis提供了mapper接口代理的开发方式,不需要再编写dao类,只需要编写一个mapper接口,一个mapper的接口和一个mapper.xml相对应,只需要调用SqlSession对象上的getMapper(),传入mapper接口的class信息,即可获得一个mapper代理对象,直接调用mapper接口中的方法,即相当于调用mapper.xml中的各个SQL标签,此时就不需要指定SQL标签的id字符串了,mapper接口中的一个方法,就对应了mapper.xml中的一个SQL标签。

1.6 两种示例(基于mapper代理与基于注解)

1.6.1 基于mapper代理示例

全局配置文件和mapper.xml文件是最基本的配置,仍然需要。不过,这次我们不编写dao类,我们直接创建一个mapper接口

package com.yogurt.mapper;

public interface StudentMapper {
	List<Student> findAll();
	int insert(Student student);
	int delete(Integer id);
	List<Student> findByName(String value);
}

而我们的mapper.xml文件如下

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

<mapper namespace="com.yogurt.mapper.StudentMapper">
    <select id="findAll" resultType="com.yogurt.po.Student">
        SELECT * FROM student;
    </select>

    <insert id="insert" parameterType="com.yogurt.po.Student">
        INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
    </insert>

    <delete id="delete" parameterType="int">
        DELETE FROM student WHERE id = #{id};
    </delete>

    <select id="findByName" parameterType="string" resultType="student">
        SELECT * FROM student WHERE name like '%${value}%';
    </select>
</mapper>

mapper接口和mapper.xml之间需要遵循一定规则,才能成功的让Mybatis将mapper接口和mapper.xml绑定起来

  • mapper接口的全限定名,要和mapper.xml的namespace属性一致
  • mapper接口中的方法名要和mapper.xml中的SQL标签的id一致
  • mapper接口中的方法入参类型,要和mapper.xml中SQL语句的入参类型一致
  • mapper接口中的方法出参类型,要和mapper.xml中SQL语句的返回值类型一致

测试代码如下

public class MapperProxyTest {
	private SqlSessionFactory sqlSessionFactory;

	@Before
	public void init() throws IOException {
		InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
	}

	@Test
	public void test() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
		List<Student> studentList = mapper.findAll();
		studentList.forEach(System.out::println);
	}
}

在这里插入图片描述
这个mapper接口,mybatis会自动找到对应的mapper.xml,然后对mapper接口使用动态代理的方式生成一个代理类

1.6.2 基于注解的示例

如果实在看xml配置文件不顺眼,则可以考虑使用注解的开发方式,不过注解的开发方式,会将SQL语句写到代码文件中,后续的维护性和扩展性不是很好(如果想修改SQL语句,就得改代码,得重新打包部署,而如果用xml方式,则只需要修改xml,用新的xml取替换旧的xml即可

使用注解的开发方式,也还是得有一个全局配置的xml文件,不过mapper.xml就可以省掉了,具体操作只用2步,如下

1.6.2.1 创建一个Mapper接口

package com.yogurt.mapper;

public interface PureStudentMapper {
	@Select("SELECT * FROM student")
	List<Student> findAll();

	@Insert("INSERT INTO student (name,age,score,gender) VALUES (#{name},#{age},#{score},#{gender})")
	int insert(Student student);
}

1.6.2.2 在全局配置文件中修改标签,直接指定加载这个类

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="properties/db.properties"></properties>
    <typeAliases>
        <package name="com.yogurt.po"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver}"/>
                <property name="url" value="${db.url}"/>
                <property name="username" value="${db.user}"/>
                <property name="password" value="${db.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="com.yogurt.mapper.PureStudentMapper"/>
    </mappers>

</configuration>

测试代码如下

public class PureMapperTest {

	private SqlSessionFactory sqlSessionFactory;

	@Before
	public void init() throws IOException {
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}

	@Test
	public void test() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		PureStudentMapper mapper = sqlSession.getMapper(PureStudentMapper.class);
		mapper.insert(new Student(10,"Tomcat",120,60,0));
        sqlSession.commit();
		List<Student> studentList = mapper.findAll();
		studentList.forEach(System.out::println);
	}
}

结果如下
在这里插入图片描述
注:当使用注解开发时,若需要传入多个参数,可以结合@Param注解,示例如下

package org.mybatis.demo.mapper;

public interface PureStudentMapper {

	@Select("SELECT * FROM student WHERE name like '%${name}%' AND major like '%${major}%'")
	List<Student> find(@Param("name") String name, @Param("major") String major);
}

@Param标签会被Mybatis处理并封装成一个Map对象,比如上面的示例中,实际传入的参数是一个Map对象,@Param标签帮忙向Map中设置了值,即它做了

Map<String,Object> map = new HashMap<>();
map.put("name", name);
map.put("major",major);

将方法形参中的name和major放到了map对象中,所以在@Select标签中可以用${name}和${major}取出map对象中的值。


上面我们见到了在全局配置文件中,两种配置mapper的方式,分别是

<!-- 在mapper接口中使用注解 -->
<mappers>
    <mapper class="com.yogurt.mapper.PureStudentMapper"/>
</mappers>

<!-- 普通加载xml -->
<mappers>
    <mapper resource="StudentMapper.xml"/>
</mappers>

而在实际工作中,一般我们会将一张表的SQL操作封装在一个mapper.xml中,可能有许多张表需要操作,那么我们是不是要在<mappers>标签下写多个<mapper>标签呢?其实不用,还有第三种加载mapper的方法,使用<package>标签

<mappers>
    <package name="com.yogurt.mapper"/>
</mappers>

这样就会自动加载com.yogurt.mapper包下的所有mapper,这种方式需要将mapper接口文件和mapper.xml文件都放在com.yogurt.mapper包下,且接口文件和xml文件的文件名要一致。注意,在IDEA的maven开发环境下,maven中还需配置<resources>标签,否则maven打包不会将java源码目录下的xml文件打包进去。

三种加载mapper的方式总结

  • <mapper resource="" />
    加载普通的xml文件,传入xml的相对路径(相对于类路径)

  • <mapper class="" />
    使用mapper接口的全限定名来加载,若mapper接口采用注解方式,则不需要xml;若mapper接口没有采用注解方式,则mapper接口和xml文件的名称要相同,且在同一个目录

  • <package name="" />
    扫描指定包下的所有mapper,若mapper接口采用注解方式,则不需要xml;若mapper接口没有采用注解方式,则mapper接口和xml文件的名称要相同,且在同一目录

注意:用后两种方式加载mapper接口和mapper.xml映射文件时,可能会报错

在这里插入图片描述
仔细检查了一下,mapper接口文件和xml映射文件确实放在了同一个目录下,而且文件名一致,xml映射文件的namespace也和mapper接口的全限定名对的上。为什么会这样呢?
在这里插入图片描述
其实是因为,对于src/main/java 源码目录下的文件,maven打包时只会将该目录下的java文件打包,而其他类型的文件都不会被打包进去,去工程目录的target目录下看看maven构建后生成的文件
在这里插入图片描述
我们需要在pom.xml中的 标签下 添加<resources>标签,指定打包时要将xml文件打包进去

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

此时再用maven进行打包,看到对应目录下有了xml映射文件(特别注意,这里配置了pom.xml下的resource标签后,可能会引发一些问题,例如原本src/main/resources资源目录下的文件没有被打包进来

我们来看下原因:
解释
至此,谜团解开。使用maven进行打包,若pom.xml中没有配置resources标签,则会采用默认策略,打包资源目录(src/main/resources)下的资源文件。若pom.xml中配置了resources标签,则完全以配置的为准。即,如果pom.xml中的resources标签只配置了扫描src/main/java,则只会扫描src/main/java,不会扫描src/main/resources,即使后者已经被标记为项目的资源目录。

先前的,仅仅配置pom.xml扫描src/main/java下的XML文件,而src/main/resources下的文件也打包了进来,这种情况,是因为一开始就进行过构建,src/main/resources目录下的文件已经被打包了进来,而后续的打包并没有执行mvn clean清除已有的文件。

所以,在使用mybatis的时候,mapper.xml文件,最好就统一放在src/main/resources资源目录下,这样就无需额外配置pom.xml。(其他资源文件也一样)。若实在需要把XML文件放在src/main/java源码目录下,那么配置pom.xml中的resources标签时,一定要记得加上src/main/resources,如下

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

            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
        </resources>
</build>

解释结束

在这里插入图片描述
此时再运行单元测试,就能正常得到结果了

1.7 应用场景

1.7.1 主键返回

通常我们会将数据库表的主键id设为自增。在插入一条记录时,我们不设置其主键id,而让数据库自动生成该条记录的主键id,那么在插入一条记录后,如何得到数据库自动生成的这条记录的主键id呢?有两种方式

1.7.1.1 使用useGeneratedKeys和keyProperty属性

<insert id="insert" parameterType="com.yogurt.po.Student" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
    </insert>

1.7.1.2 使用子标签

<insert id="insert" parameterType="com.yogurt.po.Student">
        INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
        <selectKey keyProperty="id" order="AFTER" resultType="int" >
            SELECT LAST_INSERT_ID();
        </selectKey>
    </insert>

如果使用的是mysql这样的支持自增主键的数据库,可以简单的使用第一种方式;对于不支持自增主键的数据库,如oracle,则没有主键返回这一概念,而需要在插入之前先生成一个主键。此时可以用<selectKey>标签,设置其order属性为BEFORE,并在标签体内写上生成主键的SQL语句,这样在插入之前,会先处理<selectKey>,生成主键,再执行真正的插入操作。

<selectKey>标签其实就是一条SQL,这条SQL的执行,可以放在主SQL执行之前或之后,并且会将其执行得到的结果封装到入参的Java对象的指定属性上。注意<selectKey>子标签只能用在<insert><update>标签中。上面的LAST_INSERT_ID()实际上是MySQL提供的一个函数,可以用来获取最近插入或更新的记录的主键id。

测试代码如下

public class MapperProxyTest {
	private SqlSessionFactory sqlSessionFactory;

	@Before
	public void init() throws IOException {
		InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
	}

	@Test
	public void test() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
		Student student = new Student(-1, "Podman", 130, 15, 0);
		mapper.insert(student);
		sqlSession.commit();
		System.out.println(student.getId());
	}
}

结果如下

在这里插入图片描述

1.7.2 批量查询

主要是动态SQL标签的使用,注意如果parameterType是List的话,则在标签体内引用这个List,只能用变量名list,如果parameterType是数组,则只能用变量名array

<select id="batchFind" resultType="student" parameterType="java.util.List">
        SELECT * FROM student
        <where>
            <if test="list != null and list.size() > 0">
                AND id in
                <foreach collection="list" item="id" open="(" separator="," close=")">
                    #{id}
                </foreach>
            </if>
        </where>
</select>

	@Test
	public void testBatchQuery() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
		List<Student> students = mapper.batchFind(Arrays.asList(1, 2, 3, 7, 9));
		students.forEach(System.out::println);
	}

结果

在这里插入图片描述

1.7.3 动态SQL

可以根据具体的参数条件,来对SQL语句进行动态拼接。

比如在以前的开发中,由于不确定查询参数是否存在,许多人会使用类似于where 1 = 1 来作为前缀,然后后面用AND 拼接要查询的参数,这样,就算要查询的参数为空,也能够正确执行查询,如果不加1 = 1,则如果查询参数为空,SQL语句就会变成SELECT * FROM student where,SQL不合法。

Mybatis里的动态标签主要有

1.7.3.1 if

<!-- 示例 -->
<select id="find" resultType="student" parameterType="student">
        SELECT * FROM student WHERE age >= 18
        <if test="name != null and name != ''">
            AND name like '%${name}%'
        </if>
</select>

当满足test条件时,才会将标签内的SQL语句拼接上去

1.7.3.2 choose

<!--choose和when , otherwise是配套标签 类似于java中的switch,只会选中满足条件的一个-->
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

1.7.3.3 trim

可以通过<trim>标签更加灵活地对SQL进行定制

实际上在mybatis源码,也能看到trim与set,where标签的父子关系
在这里插入图片描述

1.7.3.3.1 where

<where>标签只会在至少有一个子元素返回了SQL语句时,才会向SQL语句中添加WHERE,如果WHERE之后是以AND或OR开头,会自动将其删掉

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

<where>标签可以用<trim>标签代替

<trim prefix="WHERE" prefixOverrides="AND | OR">
   ...
</trim>
1.7.3.3.5 set

在至少有一个子元素返回了SQL语句时,才会向SQL语句中添加SET,并且如果SET之后是以","开头的话,会自动将其删掉

<set>标签相当于如下的<trim>标签

<trim prefix="SET" prefixOverrides=",">
   ...
</trim>

1.7.3.4 foreach

用来做迭代拼接的,通常会与SQL语句中的IN查询条件结合使用,注意,到parameterType为List(链表)或者Array(数组),后面在引用时,参数名必须为list或者array。如在foreach标签中,collection属性则为需要迭代的集合,由于入参是个List,所以参数名必须为list

<select id="batchFind" resultType="student" parameterType="list">
        SELECT * FROM student WHERE id in
        <foreach collection="list" item="item" open="(" separator="," close=")">
          #{item}
        </foreach>
</select>

1.7.3.5 sql

可将重复的SQL片段提取出来,然后在需要的地方,使用<include>标签进行引用

<select id="findUser" parameterType="user" resultType="user">
	SELECT * FROM user
	<include refid="whereClause"/>
</select>

<sql id="whereClause">
     <where>
         <if test user != null>
         	AND username like '%${user.name}%'
         </if>
     </where>
</sql>

1.7.3.6 bind

Mybatis的动态SQL都是用OGNL表达式进行解析的,如果需要创建OGNL表达式以外的变量,可以用bind标签

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

1.7.4 缓存

1.7.4.1 一级缓存

默认开启,同一个SqlSesion级别共享的缓存,在一个SqlSession的生命周期内,执行2次相同的SQL查询,则第二次SQL查询会直接取缓存的数据,而不走数据库,当然,若第一次和第二次相同的SQL查询之间,执行了DML(INSERT/UPDATE/DELETE),则一级缓存会被清空,第二次查询相同SQL仍然会走数据库

一级缓存在下面情况会被清除

  • 在同一个SqlSession下执行增删改操作时(不必提交),会清除一级缓存
  • SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存
  • 对mapper.xml中的某个CRUD标签,设置属性flushCache=true,这样会导致该MappedStatement的一级缓存,二级缓存都失效(一个CRUD标签在Mybatis中会被封装成一个MappedStatement)
  • 在全局配置文件中设置<setting name="localCacheScope" value="STATEMENT"/>,这样会使一级缓存失效,二级缓存不受影响

1.7.4.2 二级缓存

默认关闭,可通过全局配置文件中的<settings name="cacheEnabled" value="true"/>开启二级缓存总开关,然后在某个具体的mapper.xml中增加<cache />,即开启了该mapper.xml的二级缓存。二级缓存是mapper级别的缓存,粒度比一级缓存大,多个SqlSession可以共享同一个mapper的二级缓存。注意开启二级缓存后,SqlSession需要提交,查询的数据才会被刷新到二级缓存当中

1.7.5 关联查询

使用<resultMap>标签以及<association><collection>子标签,进行关联查询
多对一处理

多个学生,对应一个老师
对于学生这边而言,关联…多个学生,关联一个老师【多对一】
对于老师而言,集合,一个老师又很多学生【一对多】

关联-association【多对一】

集合-collection 【一对多】

1.7.5.1 多对一<association>

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);

按照查询嵌套处理

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.newer.dao.StudentMapper">
    <!--1.查询所有学生信息
        2.根据查询出来的学生的tid,寻找对应的老师
    -->
    <select id="getStudent" resultMap="StudentTeacher">
        select * from student
    </select>
 
    <resultMap id="StudentTeacher" type="Student">
        <result property="id" column="id"></result>
        <result property="name" column="name"></result>
        <!--复杂的属性,需要单独处理
        对象:association
        集合:collection
        -->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"></association>
    </resultMap>
    
    <select id="getTeacher" resultType="Teacher">
        select * from teacher where id=#{id}
    </select>
</mapper>

按照结果嵌套处理

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.newer.dao.StudentMapper">
 
    <!--按照结果嵌套处理-->
    <select id="getStudent2" resultMap="StudentTeacher2">
        select s.id sid,s.name sname,t.name tname,t.id tid
        from student s,teacher t
        where s.tid=t.id
    </select>
    <resultMap id="StudentTeacher2" type="Student">
        <result property="id" column="sid"></result>
        <result property="name" column="sname"></result>
        <association property="teacher" javaType="Teacher">
            <result property="id" column="tid"></result>
            <result property="name" column="tname"></result>
        </association>
    </resultMap>
</mapper>

回顾Mysql多对一查询方式:

  • 子查询
  • 联表查询

1.7.5.2 一对多处理<collection>

比如:一个老师拥有多个学生!

对于老师而言,就是一对多的关系!

@Data
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
}
----------------------------------------------------------------
@Data
public class Student {
private int id;
private String name;
private int tid;
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.newer.dao.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=#{tid}
    </select>
 
    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"></result>
        <result property="name" column="tname"></result>
        <collection property="students" ofType="Student">
            <result property="id" column="sid"></result>
            <result property="name" column="sname"></result>
            <result property="tid" column="tid"></result>
         </collection>
    </resultMap>
 
    <!--=====================================================-->
    //按照查询嵌套处理
    <select id="getTeacher2" resultMap="TeacherStudent2">
        select * from teacher where id=#{tid}
    </select>
    <resultMap id="TeacherStudent2" type="Teacher">
        <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId"></collection>
    </resultMap>
 
    <select id="getStudentByTeacherId" resultType="Student">
        select * from student where tid=#{tid}
    </select>
 
</mapper>

javaType & ofType

1.7.6 延迟加载

延迟加载是结合关联查询进行应用的。也就是说,只在<association><collection>标签上起作用

对于关联查询,若不采用延迟加载策略,而是一次性将关联的从信息都查询出来,则在主信息比较多的情况下,会产生N+1问题,导致性能降低。比如用户信息和订单信息是一对多的关系,在查询用户信息时,设置了关联查询订单信息,如不采用延迟加载策略,假设共有100个用户,则我们查这100个用户的基本信息只需要一次SQL查询

select * from user;

若开启了关联查询,且不是延迟加载,则对于这100个用户,会发出100条SQL去查用户对应的订单信息,这样会造成不必要的性能开销(其实我认为称之为1+N问题更为合适)

select * from orders where u_id = 1;
select * from orders where u_id = 2;
....
select * from orders where u_id = 100;

当我们可能只关心id=3的用户的订单信息,则很多的关联信息是无用的,于是,采用延迟加载策略,可以按需加载从信息,在需要某个主信息对应的从信息时,再发送SQL去执行查询,而不是一次性全部查出来,这样能很好的提升性能。

另外,针对N+1问题,除了采用延迟加载的策略按需进行关联查询。如果在某些场景下,确实需要查询所有主信息关联的从信息。在上面的例子中,就是如果确实需要把这100个用户关联的订单信息全部查询出来,那怎么办呢?这里提供2个解决思路。

1、是采用连接查询,只使用1条SQL即可,如下

select * from user as u left join orders as o on u.id = o.u_id;

但使用连接查询查出来的结果是两表的笛卡尔积,还需要自行进行数据的分组处理

2、是使用两个步骤来完成,先执行一条SQL,查出全部的用户信息,并把用户的id放在一个集合中,然后第二条SQL采用IN关键字查询即可。这种方式也可以简化为子查询,如下

select * from orders where u_id in (select id from user);

现在说回来,mybatis的延迟加载默认是关闭的,可以通过全局配置文件中的<setting name="lazyLoadingEnabled" value="true"/>来开启,开启后,所有的SELECT查询,若有关联对象,都会采用延迟加载的策略。当然,也可以对指定的某个CRUD标签单独禁用延迟加载策略,通过设置SELECT标签中的fetchType=eager,则可以关闭该标签的延迟加载

(还有一个侵入式延迟加载的概念,在配置文件中通过<setting name="aggressiveLazyLoading" value="true">来开启,大概是说,访问主对象中的主信息时,就会触发延迟加载,将从信息查询上来,这其实并不是真正意义的延迟加载,真正意义上的延迟加载应该是访问主对象中的从信息时,才触发延迟加载,去加载从信息,侵入式延迟加载默认是关闭的,一般情况下可以不用管他)

注意,延迟加载在关联查询的场景下才有意义。需要配合<resultMap>标签下的<association><collecction>标签使用

<!-- StudentMapper.xml -->
<resultMap id="studentExt" type="com.yogurt.po.StudentExt">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="score" column="score"/>
        <result property="age" column="age"/>
        <result property="gender" column="gender"/>
		<!-- 当延迟加载总开关开启时,resultMap下的association和collection标签中,若通过select属性指定嵌套查询的SQL,则其fetchType默认是lazy的,当在延迟加载总开关开启时,需要对个别的关联查询禁用延迟加载时,才有必要配置fetchType = eager -->
    	<!--
 		column用于指定用于关联查询的列
		property用于指定要封装到StudentExt中的哪个属性
		javaType用于指定关联查询得到的对象
		select用于指定关联查询时,调用的是哪一个DQL
		-->
        <association property="clazz" javaType="com.yogurt.po.Clazz" column="class_id"
                     select="com.yogurt.mapper.ClassMapper.findById" fetchType="lazy"/>

    </resultMap>

    <select id="findLazy" parameterType="string" resultMap="studentExt">
        SELECT * FROM student WHERE name like '%${value}%';
    </select>
<!-- com.yogurt.mapper.ClassMapper -->
<select id="findById" parameterType="int" resultType="com.yogurt.po.Clazz">
        SELECT * FROM class WHERE id = #{id}
</select>

/** 用于封装关联查询的对象 **/
public class StudentExt{
	private Integer id;
	private String name;
	private Integer score;
	private Integer age;
	private Integer gender;
    /** 关联对象 **/
	private Clazz clazz;
}

1.7.7 逆向工程

mybatis官方提供了mapper自动生成工具mybatis-generator-core来针对单表,生成PO类,以及Mapper接口和mapper.xml映射文件。针对单表,可以不需要再手动编写xml配置文件和mapper接口文件了,非常方便。美中不足的是它不支持生成关联查询。一般做关联查询,就自己单独写SQL就好了。

基于IDEA的mybatis逆向工程操作步骤如下:

1.7.7.1 配置maven插件

<build>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <configuration>
                    <!-- 输出日志 -->
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>
    </build>

1.7.7.2 在resources目录下创建名为generatorConfig.xml的配置文件

在这里插入图片描述

1.7.7.3 配置文件的模板如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!--导入属性配置-->
    <properties resource="properties/xx.properties"></properties>

    <!-- 指定数据库驱动的jdbc驱动jar包的位置 -->
    <classPathEntry location="C:\Users\Vergi\.m2\repository\mysql\mysql-connector-java\8.0.11\mysql-connector-java-8.0.11.jar" />
    <!-- context 是逆向工程的主要配置信息 -->
    <!-- id:起个名字 -->
    <!-- targetRuntime:设置生成的文件适用于那个 mybatis 版本 -->
    <context id="default" targetRuntime="MyBatis3">
        <!--optional,旨在创建class时,对注释进行控制-->
        <commentGenerator>
            <property name="suppressDate" value="true" />
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>

        <!--jdbc的数据库连接-->
        <jdbcConnection driverClass="${db.driver}"
                        connectionURL="${db.url}"
                        userId="${db.user}"
                        password="${db.password}">
        </jdbcConnection>


        <!--非必须,类型处理器,在数据库类型和java类型之间的转换控制-->
        <javaTypeResolver>
            <!-- 默认情况下数据库中的 decimal,bigInt 在 Java 对应是 sql 下的 BigDecimal 类 -->
            <!-- 不是 double 和 long 类型 -->
            <!-- 使用常用的基本类型代替 sql 包下的引用类型 -->
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- targetPackage:生成的实体类所在的包 -->
        <!-- targetProject:生成的实体类所在的硬盘位置 -->
        <javaModelGenerator targetPackage="mybatis.generator.model"
                            targetProject=".\src\main\java">
            <!-- 是否允许子包 -->
            <property name="enableSubPackages" value="false" />
            <!-- 是否清理从数据库中查询出的字符串左右两边的空白字符 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!-- targetPackage 和 targetProject:生成的 mapper.xml 文件的包和位置 -->
        <sqlMapGenerator targetPackage="mybatis.generator.mappers"
                         targetProject=".\src\main\resources">
            <!-- 针对数据库的一个配置,是否把 schema 作为字包名 -->
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>

        <!-- targetPackage 和 targetProject:生成的 mapper接口文件的包和位置 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="mybatis.generator.dao" targetProject=".\src\main\java">
            <!-- 针对 oracle 数据库的一个配置,是否把 schema 作为子包名 -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>
        <!-- 这里指定要生成的表 -->
        <table tableName="student"/>
        <table tableName="product"/>
    </context>
</generatorConfiguration>

1.7.7.4 双击执行mybatis-generator的maven插件

在这里插入图片描述

1.7.7.5 查看结果

执行日志如下
在这里插入图片描述
生成的文件如下
在这里插入图片描述
能看到mybatis-generator除了给我们生成了基本的PO类(上图的Student和Product),还额外生成了Example类。Example类是为了方便执行SQL时传递查询条件的。使用的示例如下

public class GeneratorTest {
	private SqlSessionFactory sqlSessionFactory;

	@Before
	public void init() throws IOException {
		InputStream resourceAsStream = Resources.getResourceAsStream("mysql8-config.xml");
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
	}

	@Test
	public void test() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
		StudentExample example = new StudentExample();
		StudentExample.Criteria criteria = example.createCriteria();
		criteria.andNameLike("%o%");
		List<Student> students = mapper.selectByExample(example);
		students.forEach(System.out::println);
	}
}

结果如下
在这里插入图片描述

1.7.8 PageHelper分页插件

使用该插件,快速实现查询结果的分页,使用步骤如下

pom.xml中配置依赖

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

mybatis全局配置文件中配置<plugin>标签

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="properties/xx.properties"></properties>

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>

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

    <mappers>
        <package name="mybatis.generator.dao"/>
    </mappers>

</configuration>

在执行查询之前,先设置分页信息

// 查询第一页,每页3条信息
PageHelper.startPage(1,3);

先看一下查所有数据

	@Test
	public void test() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
		//PageHelper.startPage(1,3);
		List<Product> products = mapper.selectByExample(new ProductExample());
		products.forEach(System.out::println);
	}

在这里插入图片描述
加上PageHelper分页

	@Test
	public void test() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
		PageHelper.startPage(1,3);
		List<Product> products = mapper.selectByExample(new ProductExample());
		products.forEach(System.out::println);
	}

在这里插入图片描述
特别注意:在编写mapper.xml的时候,SQL语句的结尾不要带上;,因为PageHelper插件是在SQL末尾拼接LIMIT关键字来进行分页的,若SQL语句带上了;,就会造成SQL语法错误
在这里插入图片描述
另外,PageHelper会先查询总数量,然后再发出分页查询,打开mybatis的日志时,可以看到发出了2条SQL
在这里插入图片描述
当开启PageHelper时,查询得到的List实际是PageHelper中自定义的一个类Page,这个类实现了List接口,并封装了分页的相关信息(总页数,当前页码等)
在这里插入图片描述
可以通过PageInfo来获取分页的相关信息,代码如下

@Test
public void test() {
	SqlSession sqlSession = factory.openSession();
	PageHelper.startPage(1,3);
	ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
	List<Product> list = mapper.findAll();
	list.forEach(System.out::println);
	PageInfo<Product> pageInfo = new PageInfo<>(list);
	System.out.println(pageInfo.getTotal()); // 获得总数
	System.out.println(pageInfo.getPageSize());  // 获得总页数
}

1.7.9 Mybatis Plus

mybatis虽然非常方便,但也需要编写大量的SQL语句,于是mybatis plus就应运而生了。它是一个mybatis增强工具,为了简化开发,提高效率。

举报

相关推荐

0 条评论