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 | setterpublic 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 mapperPUBLIC "-//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 empSalaryFROM t_empWHERE 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 configurationPUBLIC "-//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方式进行数据库操作
- 不要求写对应的mapper接口
- 直接创建mapper.xml文件,在内部编写xml文件
- namespace属性没有要求,随便写一个字符串
- 内部通过crud标签声明对应的sql语句即可
StudentMapper.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//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 empSalaryFROM t_empWHERE 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属性值的语法:
①类的全限定符
②别名的简称
别名 | 映射的类型 |
---|---|
_byte | byte |
_char (since 3.5.10) | char |
_character (since 3.5.10) | char |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
char (since 3.5.10) | Character |
character (since 3.5.10) | Character |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
biginteger | BigInteger |
object | Object |
object[] | Object[] |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
如果类没有别名,则需要使用全限定符或自定义别名
自定义别名方法:(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 empSalaryFROM 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">SELECTemp_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 empSalaryfrom 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标签的返回值类型