侧边栏壁纸
博主头像
LYMTICS

海纳百川,有容乃大

  • 累计撰写 45 篇文章
  • 累计创建 37 个标签
  • 累计收到 19 条评论

目 录CONTENT

文章目录

MyBatis学习笔记

LYMTICS
2021-08-31 / 0 评论 / 0 点赞 / 81 阅读 / 14,574 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-02-23,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

MyBatis学习笔记

相关学习视频:动力节点MyBatis教程实战精讲

官方中文文档:MyBatis 3

作用

减轻使用 JDBC 的复杂性,不用编写重复的创建 Connetion , Statement ; 不用编写关闭资源代码。

直接使用 java 对象,表示结果数据。让开发者专注 SQL 的处理。 其他分心的工作由 MyBatis 代劳。

使用

准备

创建一个表

CREATE TABLE `student` (
 `id` int(11) NOT NULL ,
 `name` varchar(255) DEFAULT NULL,
 `email` varchar(255) DEFAULT NULL,
 `age` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建项目

使用IDEA创建一个普通Maven项目。

加入如下依赖(版本自行修改):

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.27</version>
</dependency>
<!--加个lombok方便用-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
</dependency>

Maven 默认会过滤掉其他格式的文件,无法打包 XML 格式的文件到输出中,所以需要手动加入:

<build>
    <resources>
        <resource>
            <!--所在的目录-->
            <directory>src/main/java</directory>
            <!--包括目录下的.properties,.xml 文件都会扫描到-->
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>

修改配置

resources/ 目录下创建一个 XML 文件,例如 mybatis.xml , 在里面添加如下的配置信息:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--环境配置信息,可以有多个环境,只生效一个-->
    <environments default="development">
        <!--其中一个环境-->
        <environment id="development">
            <!--mybatis的事务类型-->
            <!--表示用JDBC中Connection对象的commit, rollback做事务处理-->
            <transactionManager type="JDBC"/>
            <!--表示数据源,连接数据库的 type 表示数据源的类型 POOLED 表示使用连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://test.com:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="abc123"/>
            </dataSource>
        </environment>
    </environments>
    <!--SQL映射文件 SQL Mapper 的位置-->
    <mappers>
        <!--一个mapper标签指定一个文件的位置-->
        <!--从类路径开始的路径信息-->
        <mapper resource="cn/ethy/dao/StudentDao.xml"/>
    </mappers>
</configuration>

入门案例

image

创建实体类

package entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@ToString
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private int id;
    private String name;
    private String email;
    private int age;
}

DAO

StudentDao.java :编写接口

package cn.ethy.dao;

import cn.ethy.entity.Student;

import java.util.List;

public interface StudentDao {
    List<Student> selectStudents();
}

StudentDao.xml:编写SQL语言

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.ethy.dao.StudentDao">
    <select id="selectStudents" resultType="cn.ethy.entity.Student">
        SELECT * FROM student ORDER BY id
    </select>
</mapper>

主程序

Test.java :主程序

public class Test {

    @Test
    public void test01() throws IOException {
        // 1. 定义MyBatis主配置文件的名称
        String config = "mybatis.xml";
        // 2. 读取这个 config 表示的文件
        InputStream in = Resources.getResourceAsStream(config);
        // 3. 创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        // 4. 创建SqlSessionFactory对象
        SqlSessionFactory factory = builder.build(in);
        // 5. 从SqlSessionFactory中获取SqlSession对象
        SqlSession sqlSession = factory.openSession();
        // 6. 指定要执行的SQL语句的标识 SQL映射文件中的namespace + "." + 标签的ID值
        String sqlId = "cn.ethy.dao.StudentDao.selectStudents";
        // 7. 执行SQL语句,通过SQL ID找到语句
        List<Student> studentList = sqlSession.selectList(sqlId);
        // 8. 输出结果
        studentList.forEach(System.out::println);
        // 9. 关闭SQLSession
        sqlSession.close();
    }
}

是不是又臭又长?

循序学习

插入数据

StudentDao.java :编写接口

// 插入
int insertStudent(Student student);

StudentDao.xml:编写SQL语言

<insert id="insertStudent">
    INSERT INTO student VALUES(#{id}, #{name}, #{email}, #{age})
</insert>

注意这里用到了 ${} 的形式来获取参数,这里的值应该和实体类中的变量名称相同,因为他是用对应名称的Get方法来操作的,如果错了,就会报错:

Error updating database.  Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'mail' in 'class cn.ethy.entity.Student'

大小写也有影响。

Test.java : 测试文件

int i = sqlSession.insert(sqlId, 
           new Student(1111, "吕布", "asdf@aaa.com", 12));
// 提交后才能看到
sqlSession.commit();

开启日志

mybatis.xml<configuration> 标签下添加如下配置:

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

用到的类

Resources : mybatis中的一个类,负责读取主配置文件

InputStream in = Resources.getResourceAsStream(config);

SqlSessionFactoryBuilder : 创建SqlSessionFactory对象

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);

SqlSessionFactory : 用来获取SqlSession对象。重量级对象,程序创建一个对象耗时比较长,使用的资源比较多,在整个项目中,有一个就够了。

SqlSessionFactory本身是一个接口,下面有许多实现类

SqlSession sqlSession = factory.openSession();
// 可以携带一个参数,表示是否开启自动提交,默认不会自动提交
SqlSession sqlSession = factory.openSession(true);

SqlSession : SqlSession接口定义了操作数据的方法,如 selectOneupdate 等;

实现类有:DefaultSqlSession。

注意:SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close() 方法,将其关闭。再次需要会话,再次创建。 SqlSession 在方法内部创建,使用完毕后关闭。

封装工具类

utils.MyBatisUtil.java :确保只有一个 SqlSession:

package cn.ethy.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisUtil {

    private static SqlSessionFactory factory = null;

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

    public static SqlSession getSqlSession() {
        SqlSession sqlSession = null;
        if (factory != null) {
            sqlSession = factory.openSession();
        }
        return sqlSession;
    }
}

DAOImpl

前面 XXXDao.java 的接口并没有用上,这里尝试完成他们的实现类,如:

StudentDaoImpl.java

package cn.ethy.dao.impl;

import cn.ethy.dao.StudentDao;
import cn.ethy.entity.Student;
import cn.ethy.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class StudentDaoImpl implements StudentDao {

    @Override
    public List<Student> selectStudents() {
        SqlSession session = MyBatisUtil.getSqlSession();
        List<Student> studentList = session.selectList("cn.ethy.dao.StudentDao.selectStudents");
        session.close();
        return studentList;
    }

    @Override
    public int insertStudent(Student student) {
        SqlSession session = MyBatisUtil.getSqlSession();
        int result = session.insert("cn.ethy.dao.StudentDao.insertStudent", student);
        session.close();
        return result;
    }
}

Test.java

@Test
public void test03() {
    StudentDao studentDao = new StudentDaoImpl();
    studentDao.insertStudent(new Student(2921, "马克", "asdf@allx.com", 22));
    studentDao.selectStudents().forEach(System.out::println);
    session.close();
}

如果这样的话,有很多步骤都是重复的,或者说是有某种规律的,那么我们能否把这种规律找出来呢?

Mapper

其实,我们无需自己编写实现类,我们在XML文件中已经提供了很多信息,利用这些信息,MyBatis可以利用动态代理机制自动生成对应的实现文件:

@Test
public void test04() {
    SqlSession session = MyBatisUtil.getSqlSession();
    StudentDao dao = session.getMapper(StudentDao.class);
    dao.selectStudents().forEach(System.out::println);
    session.close();
}

传递参数

parameterType :告诉MyBatis返回值的类型

<select id="selectStudents" resultType="cn.ethy.entity.Student" parameterType="java.lang.Integer">
    SELECT * FROM student WHERE id = #{id}
</select>

由于一些常用的类型这样太长了,所以这些类型有简称(别名)(点击查看更多别名)

所以上面你也可以写成 intInteger

另外,这个参数也不是强制的, mybatis可以通过反射机制发现接口参数的类型,所以可以没有。一般我们也不写。

一个简单参数

简单: 基础类型及其包装类,以及String类型

占位符: #{任意非空字符}

多个参数

命名参数【官推】

官方推荐!

XXDao.java

public List<Student> selectMultiParam(@Param("name") String name, @Param("age") Integer age);

XXDao.xml

<select resultType="cn.ethy.entity.Students">
    SELECT * FROM students WHERE name = #{name} AND age = #{age};
</select>

使用对象【掌握】

创建一个参数类,如 QueryParam.java

public class QueryParam {
    private String paramName;
    private String paramAge;
}

XXDao.xml

<select resultType="cn.ethy.entity.Students">
    SELECT * FROM students WHERE name= #{paramName} AND age= #{paramAge};
</select>

完整形式:

#{属性名, javaType=类型名称, jdbcType=数据类型}
如:
#{paramAge, javaType=java.lang.Integer, jdbcType=INTEGER}

后两者 mybatis 可以通过反射获取,所以无需提供

或者也可以直接用Student对象,就如上面的一个例子

按位置【了解】

mybatis-3.3 版本和之前的版本使用#{0},#{1}方式

从 mybatis3.4 开始使用#方式

Map【了解】

Map<String,Object> data = new HashMap<String,Object>();
data.put(“myname”,”李力”);
data.put(“myage”,20);
<select id="selectMultiMap" resultType="cn.ethy.entity.Student">
 select id,name,email,age from student where name=#{myname} or age =#{myage}
</select>

$和#

$ 使用的是 Statement,有安全注入的问题

# 使用的是 PrepareStatement,预编译

如果你能确定参数是安全的,则可以用前者,比如代替列名等(不是用户输入)

模糊查询

方式一:

在Java代码中提供:

@Test
public void testSelectLikeOne(){
     String name="%力%";
     List<Student> stuList = studentDao.selectLikeFirst(name);
     stuList.forEach( stu -> System.out.println(stu));
}
<select id="selectLikeFirst" resultType="com.bjpowernode.domain.Student">
     select id,name,email,age from student
     where name like #{studentName}
</select>

方式二:

mapper文件中使用,类似 like name "%" #{XXX} "%" 的形式

注意其中的空格

@Test
public void testSelectLikeSecond(){
     String name="力";
     List<Student> stuList = studentDao.selectLikeSecond(name);
     stuList.forEach( stu -> System.out.println(stu));
}
<select id="selectLikeSecond" resultType="com.bjpowernode.domain.Student">
     select id,name,email,age from student
     where name like "%" #{studentName} "%"
</select>

返回结果

resultType : 执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。resultType 和 resultMap 不能同时使用。

简单类型

同上面 parameterType 的叙述

对象类型

同上面 parameterType 的叙述

Map类型

案例:

Map<Object, Object> selectReturnMap(int id);
<select id="selectReturnMap" resultType="java.util.HashMap">
	select name,email from student where id = #{studentId}
</select>

Map 作为接口返回值,sql 语句的查询结果最多只能有一条记录。大于一条记录是错误。

resultMap

resultMap 可以自定义 sql 的结果和 java 对象属性的映射关系。更灵活的把列值赋值给指定属性。常用在列名和 java 对象属性名不一样的情况。

<resultMap id="studentMap" type="com.bjpowernode.domain.Student">
    <!-- 主键字段使用 id -->
    <id column="id" property="id" />
    <!--非主键字段使用 result-->
    <result column="name" property="name"/>
    <result column="email" property="email" />
    <result column="age" property="age" />
</resultMap>


<!--resultMap: resultMap 标签中的 id 属性值--> <select id="selectUseResultMap" resultMap="studentMap">
 select id,name,email,age from student where name=#{queryName} or 
age=#{queryAge}
</select>

如上所示,还可以多次重复利用。

类属性名和列名不同

有两种处理方法:

  1. 使用上面提到的 resultMap 方法进行处理
  2. 在 SQL 语句中起别名,如 select name as stuName ... 之类的

动态SQL

动态 SQL,主要用于解决查询条件不确定的情况:在程序运行期间,根据用户提交的查询条件进行查询。提交的查询条件不同,执行的 SQL 语句不同。若将每种可能的情况均逐一列出,对所有条件进行排列组合,将会出现大量的 SQL 语句。此时,可使用动态 SQL 来解决这样的问题。也可以减少字符串拼接的复杂操作

XML 中部分特殊字符应该转义:

原来转义后
<&lt;
>&gt;
>=&gt;=
<=&lt;=

if

<select id="selectStudentIf" resultType="com.bjpowernode.domain.Student">
 select id,name,email,age from student
 where 1=1
 <if test="name != null and name !='' ">
 and name = #{name}
 </if>
 <if test="age > 0 ">
 and age &gt; #{age}
 </if>
</select>

为什么要 1 = 1 ? 因为如果没有这个语句,且下面的某个 if 块不成立,SQL语句会有多余的 AND,造成 SQL 语法错误。

也可以用下面的 where 来解决这个问题:

where

<if/> 标签的中存在一个比较麻烦的地方:需要在 where 后手工添加 1=1 的子句。因为,若 where 后的所有 <if/>条件均为 false,而 where 后若又没有 1=1 子句,则 SQL 中就会只剩下一个空的 where,SQL出错。所以,在 where 后,需要添加永为真子句 1=1,以防止这种情况的发生。但当数据量很大时,会严重影响查询效率。

使用 <where/> 标签,在有查询条件时,可以自动添加上 where 子句;没有查询条件时,不会添加where 子句。需要注意的是,第一个 <if/> 标签中的 SQL 片断,可以不包含 and。不过,写上 and 也不错,系统会将多出的 and 去掉。但其它 <if/> 中 SQL 片断的 and,必须要求写上。否则 SQL 语句将拼接出错。

<select id="selectStudentWhere" resultType="com.bjpowernode.domain.Student">
 select id,name,email,age from student
     <where>
         <if test="name != null and name !='' ">
         and name = #{name}
         </if>
         <if test="age > 0 ">
         and age &gt; #{age}
         </if>
     </where>
</select>

foreach

<foreach/> 标签用于实现对于数组与集合的遍历。对其使用,需要注意:

  • collection 表示要遍历的集合类型, list ,array 等。
  • open、close、separator 为对遍历内容的 SQL 拼接。
<foreach collection="集合类型" open="开始的字符" close="结束的字符" 
item="集合中的成员" separator="集合成员之间的分隔符">
 #{item 的值}
</foreach>

案例:

<select id="selectStudentForList2" 
resultType="com.bjpowernode.domain.Student">
 select id,name,email,age from student
     <if test="list !=null and list.size > 0 ">
     where id in
         <foreach collection="list" open="(" close=")" 
        item="stuobject" separator=",">
         #{stuobject.id}
         </foreach>
     </if>
</select>

代码片段

定义 SQL 片段,以方便代码复用

<!--创建 sql 片段 id:片段的自定义名称--> 
<sql id="studentSql">
 select id,name,email,age from student
</sql>


<select id="selectStudentSqlFragment" resultType="com.bjpowernode.domain.Student">
 <!-- 引用 sql 片段 -->
 	<include refid="studentSql"/>
     <if test="list !=null and list.size > 0 ">
     		where id in
         <foreach collection="list" open="(" close=")" 
        item="stuobject" separator=",">
            #{stuobject.id}
         </foreach>
     </if>
</select>

配置补充

外部属性配置文件

resources/jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?charset=UTF-8
jdbc.username=root
jdbc.password=123456

修改 mybatis.xml

<properties resource="jdbc.properties"></properties>
<dataSource type="POOLED">
 <!--使用 properties 文件: 语法 ${key}-->
 <property name="driver" value="${jdbc.driver}"/>
 <property name="url" value="${jdbc.url}"/>
 <property name="username" value="${jdbc.username}"/>
 <property name="password" value="${jdbc.password}"/>
</dataSource>

类型别名

 <!--
 定义单个类型的别名
 type:类型的全限定名称
 alias:自定义别名 
 -->
北京动力节点 www.bjpowernode.com
北京动力节点 www.bjpowernode.com
 <typeAlias type="com.bjpowernode.domain.Student"  alias="mystudent"/>
 <!--
 批量定义别名,扫描整个包下的类,别名为类名(首字母大写或小写都可以)
 name:包名 
 -->
 <package name="com.bjpowernode.domain"/>
 <package name="...其他包"/>
</typeAliases>

批量映射

之前这样一次只能映射一个 Mapper 文件:

<mappers>
    <!--一个mapper标签指定一个文件的位置-->
    <!--从类路径开始的路径信息-->
    <mapper resource="cn/ethy/dao/StudentDao.xml"/>
</mappers>

这样可以映射一个包:

<mappers>
    <package name="cn.ethy.dao"/>
</mappers>
0

评论区