SSM框架,MyBatis框架的学习(上)

118 阅读12分钟

MyBatis简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

持久层框架对比

  • JDBC

    • SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和 JPA

    • 操作简便,开发效率高
    • 程序中的长难复杂 SQL 需要绕过框架
    • 内部自动生成的 SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
    • 反射操作太多,导致数据库性能下降
  • MyBatis

    • 轻量级,性能出色
    • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
    • 开发效率稍逊于 Hibernate,但是完全能够接收

开发效率:Hibernate>Mybatis>JDBC

运行效率:JDBC>Mybatis>Hibernate

MyBatis 框架下,SQL语句编写位置发生改变,从原来的Java类,改成XML或者注解定义

推荐在XML文件中编写SQL语句,让用户能更专注于 SQL 代码,不用关注其他的JDBC代码。

Mybatis 中的 Mapper 接口相当于以前的 Dao。但是区别在于,Mapper 仅仅只是建接口即可,我们不需要提供实现类,具体的SQL写到对应的Mapper文件

MyBatis案例:

Employee类:

package com.ergou.pojo;

public class Employee {

    private Integer empId;

    private String empName;

    private Double empSalary;

//getter | setter

public Integer getEmpId() {
        return empId;
    }

    public void setEmpId(Integer empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    public Double getEmpSalary() {
        return empSalary;
    }

    public void setEmpSalary(Double empSalary) {
        this.empSalary = empSalary;
    }
}

Mapper接口:

package com.ergou.mapper;

import com.ergou.pojo.Employee;

public interface EmployeeMapper {
//根据id查询员工信息
Employee queryById(Integer id);
//根据id删除员工
int deleteById(Integer id);
}

EmployeeMapper.xml:(sql语句写在其中)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "<https://mybatis.org/dtd/mybatis-3-mapper.dtd>">
<!-- namespace等于mapper接口类的全限定名,这样实现对应-->
<!--xml方式写sql语句
mybatis固定在特定的标签内写sql语句
mapper的xml文件要有约束-->
<mapper namespace="com.ergou.mapper.EmployeeMapper">

<!--查询使用 select标签
            id =方法名
            resultType =返回值类型
            标签内编写SQL语句
            mapper接口下不能方法重载,否则全限定符对应不到唯一的方法
-->
<select id="queryById" resultType="com.ergou.pojo.Employee">
<!-- #{empId}代表动态传入的参数,并且进行赋值!后面详细讲解-->
SELECT emp_id empId,emp_name empName, emp_salary empSalary
        FROM t_emp
        WHERE emp_id = #{empId}
    </select>
    <delete id="deleteById">
        DELETE FROM t_emp WHERE emp_id = #{id}
    </delete>
</mapper>

mybatis-config.xml:(配置mybatis框架)

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

<!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。-->
<environments default="development">
<!-- environment表示配置Mybatis的一个具体的环境-->
<environment id="development">
<!-- Mybatis的内置的事务管理器-->
<transactionManager type="JDBC"/>
<!--配置数据源-->
<dataSource type="POOLED">
<!--建立数据库连接的具体信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
                <property name="username" value="liergou"/>
                <property name="password" value="liergou070509"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
<!-- Mapper注册:指定Mybatis映射文件的具体位置-->
        <!-- mapper标签:配置一个具体的Mapper映射文件-->
        <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径-->
        <!--对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准-->
<mapper resource="mappers/EmployeeMapper.xml"/>
    </mappers>

</configuration>

测试:

@Test
public void test_01() throws IOException {
//1.读取外部配置文件
InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
//3.根据sqlSessionFactory创建sqlSession(每次业务创建一个,用完就释放)
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.获取接口的代理对象
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//调用方法
Employee employee = mapper.queryById(1);
    System.out.println(employee);
//5.提交事务(非DQL)和释放资源
sqlSession.commit();
    sqlSession.close();
}

ibatis方式和原理

mybatis进行数据库的crud是对ibatis的封装和优化

ibatis方式进行数据库操作

  1. 不要求写对应的mapper接口
  2. 直接创建mapper.xml文件,在内部编写xml文件
  3. namespace属性没有要求,随便写一个字符串
  4. 内部通过crud标签声明对应的sql语句即可

StudentMapper.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "<https://mybatis.org/dtd/mybatis-3-mapper.dtd>">
<!--namespace:mapper对应接口的全限定符-->
<!--ibatis方式进行数据库操作
    1.不要求写对应的mapper接口
    2.直接创建mapper.xml文件,在内部编写xml文件
    3.namespace属性没有要求,随便写一个字符串-->
<mapper namespace="StudentMapper">
    <select id="query" resultType="com.ergou.pojo.Student">
        SELECT * FROM student WHERE sid = #{id}
    </select>
</mapper>

在对应的config.xml文件中的mapper标签中配置StudentMapper.xml

<mappers>
<!-- Mapper注册:指定Mybatis映射文件的具体位置-->
    <!-- mapper标签:配置一个具体的Mapper映射文件-->
    <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径-->
    <!--对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准-->
<mapper resource="mappers/EmployeeMapper.xml"/>
    <mapper resource="mappers/StudentMapper.xml"/>
</mappers>

测试:

@Test
public void test_02() throws IOException {
//1.读取外部配置文件
InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
//3.根据sqlSessionFactory创建sqlSession(每次业务创建一个,用完就释放)
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.直接sqlSession提供的crud方法进行数据库查询即可
//方法名为:select / insert / delete / update
    //selectOne和selectList方法:参数1为sql标签对应的id值,参数二为执行sql语句传入的参数
Student student = sqlSession.selectOne("StudentMapper.query", 1);
    System.out.println(student);
//5.提交事务(非DQL)和释放资源
sqlSession.commit();
    sqlSession.close();
}

缺点:其中的selectOne和selectList方法只能传一个参数

而在mybatis中,可以自定义相应的sql语句和相应的方法

mybatis基本使用

向sql语句传参

#{}

#{}是作为占位符给指定目标赋值,当值传入占位符时,就会给指定目标传入此值

${}

${}是作为拼接块,将传入的值以字符串的形式拼接在sql语句后

建议使用#{},防止外界sql注入而改变sql语句原本的意思

${}在动态的列名、表名、关键字等处使用

//注解方式传入参数!!
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column,
                                @Param("value") String value);

数据输入

简单类型

这里的简单类型指的是单值的类型的变量,比如基本数据类型和字符串类型,一个变量中只有一个数据值。

<!--若传入的是单个简单类型值,#{}中间的名字随便写,因为传入的值为单个值的数据,不存在需要辨别-->
<delete id="deleteById">
        DELETE FROM t_emp WHERE emp_id = #{id}
</delete>

单个实体对象传入

若传入的是单个实体对象:#{}中间的名字要与要传入的值的的属性名一致,以方便对应赋值

<!--若传入的是单个实体对象,#{}中间的名字要与要传入的值的的属性名一致,以方便对应赋值-->
<insert id="insertEmployee">
        INSERT INTO t_emp(emp_name,emp_salary)
        VALUES(#{empName},#{empSalary})
</insert>

多个简单类型传入

方式①:使用注解@Param指定多个简单参数的key(在#{}中使用的名称)

//根据员工姓名和工资查询员工信息
List<Employee> queryByNameAndSalary(@Param("a") String name,@Param("b") Double salary);

<!--若传入的是多个简单类型的值-->
<select id="queryByNameAndSalary" resultType="com.ergou.pojo.Employee">
        SELECT emp_id empId,emp_name empName,emp_salary empSalary
        FROM t_emp
        WHERE emp_name = #{a} AND emp_salary = #{b}
</select>

方案②:mybatis默认机制,使用默认的名字arg0,arg1,……(形参列表中每个参数的key名字默认为arg0,arg1,arg2,以此类推(以0开头)),或使用param1,param2,……(默认以1开头)

Map类型参数传入

若传入的是Map类型的数据,#{}中直接写Map集合中要传入的value值对应的key值即可

<!--若传入的是Map类型的数据,#{}中直接写Map集合中要传入的value值对应的key值即可-->
<insert id="insertEmployeeMap">
        INSERT INTO t_emp(emp_name,emp_salary)
        VALUES(#{name},#{salary})
</insert>

数据输出

数据输出总体上有两种形式:                 

  • 增删改操作返回的受影响行数:直接使用 int 或 long 类型接收即可
  • 查询操作的查询结果

单个简单类型

若是DML语句,返回受影响行数,则不需要手动指定返回值类型

<!--若是DML语句,返回受影响行数,则不需要手动指定返回值类型-->
<delete id="deleteById">
        DELETE FROM t_emp WHERE emp_id = #{id}
</delete>

若是查询语句,需要指定输出类型,使用resultType属性值,在相应的select标签中指定返回值类型

resultType属性值的语法:

①类的全限定符

②别名的简称

别名映射的类型
_bytebyte
_char (since 3.5.10)char
_character (since 3.5.10)char
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
char (since 3.5.10)Character
character (since 3.5.10)Character
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
bigintegerBigInteger
objectObject
object[]Object[]
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection

如果类没有别名,则需要使用全限定符或自定义别名

自定义别名方法:(typeAliases标签写在settings标签下)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases> <package name="domain.blog"/> </typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有@Alias注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有@Alias注解,则别名为其注解指定的值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

单个实体类型

<!--返回单个自定义实体类型-->
<select id="queryById" resultType="com.ergou.pojo.Employee">
        SELECT emp_id empId,emp_name empName,emp_salary empSalary
        FROM t_emp WHERE emp_id = ${id}
    </select>

默认要求:返回单个实体类型时,列名和属性名要对应且一致,才能进行实体类的属性映射

也可以进行设置(在mybatis-config.xml文件中进行),设置支持驼峰式自动映射,即自动将下划线格式转化为驼峰格式,例:将emp_id转化为empId

<!-- 在全局范围内对Mybatis进行配置 -->
<settings>

  <!-- 具体配置 -->
  <!-- 从org.apache.ibatis.session.Configuration类中可以查看能使用的配置项 -->
  <!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 -->
  <!-- 规则要求数据库表字段命名方式:单词_单词 -->
  <!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 -->
  <setting name="mapUnderscoreToCamelCase" value="true"/>

</settings>

Map类型

适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。

<!-- Map<String,Object> selectEmpNameAndMaxSalary(); -->
<!-- 返回工资最高的员工的姓名和他的工资 -->
<select id="selectEmpNameAndMaxSalary" resultType="map">
  SELECT
    emp_name 员工姓名,
    emp_salary 员工工资,
    (SELECT AVG(emp_salary) FROM t_emp) 部门平均工资
  FROM t_emp WHERE emp_salary=(
    SELECT MAX(emp_salary) FROM t_emp
  )
</select>

List类型

若返回值是集合,resultType不需要指定集合类型,只需要指定泛型即可

<!-- List<Employee> selectAll(); -->
<select id="selectAll" resultType="com.atguigu.mybatis.entity.Employee">
  select emp_id empId,emp_name empName,emp_salary empSalary
  from t_emp
</select>

关于主键

自增长主键回显:(由mysql的auto_increment自动产生的主键值)

使用userGeneratedKeys属性,将其设置为true

在keyColumn属性中写上主键的列名(可省略)

在keyProperty属性中指定存储主键的属性名

最终生成的对象会多一个其属性名为keyProperty指定的属性名的属性,里面存储着插入对象的的主键值

<!-- int insertEmployee(Employee employee); -->
<!-- useGeneratedKeys属性字面意思就是“使用生成的主键” -->
<!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 -->
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
  insert into t_emp(emp_name,emp_salary)
  values(#{empName},#{empSalary})
</insert>

非自增长类型主键的维护:(不是自动增长的主键值)

在insert标签中使用selectKey标签,在其中写一段sql语句(查询),关于select标签:

属性:

  • order:sql语句是在插入语句之前还是之后进行
  • resultType:返回值类型
  • keyProperty:查询结果给哪个属性赋值,这个属性可以给insert标签中的sql语句使用,同时在生成的相应java对象中也会多一个此属性,可以通过get方法获取

实体类属性和数据库字段对应关系

当列名和属性名不一致,有以下解决方案:

①给查询的列名起别名,此别名和属性名一致

②使用驼峰式映射

<!-- 使用settings对Mybatis全局进行设置 -->
<settings>

  <!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 -->
  <setting name="mapUnderscoreToCamelCase" value="true"/>

</settings>

③resultMap自定义映射

resultType按照规则自动映射:按照是否开启驼峰式映射,自己映射属性和列名,只能映射一层结构(即一个对象的属性是其他类的对象,该属性的属性不会自动映射)

resultMap自定义映射:使用resultMap标签,result标签就是一个中间处理的标签,处理数据库的列名和属性名之间的映射

<!-- 专门声明一个resultMap设定column到property之间的对应关系 -->
<!--属性:
					id:供select标签的selectMap属性调用的值-->
<resultMap id="selectEmployeeByRMResultMap" type="com.atguigu.mybatis.entity.Employee">

  <!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
  <!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
  <id column="emp_id" property="empId"/>

  <!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
  <result column="emp_name" property="empName"/>

  <result column="emp_salary" property="empSalary"/>

</resultMap>

<!-- Employee selectEmployeeByRM(Integer empId); 
属性改为使用resultMap属性-->
<select id="selectEmployeeByRM" resultMap="selectEmployeeByRMResultMap">

  select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}

</select>

select标签中调用了result标签,result标签中的type属性指定了返回值类型,间接指定了select标签的返回值类型