Mybatis
为什么使用Mybatis?
- JDBC的缺点
- SQL夹杂在Java代码块中,耦合度高,修改更新繁琐
- 维护不容易,实际开发中sql有变化修改困难
- Hibernate和JPA的缺点
- Hibernate难处理长难的SQL
- 内部自动生成的SQL,要做特殊优化困难
- 基于全映射的自动框架,大量字段的POJO要进行部分映射时比较困难,导致数据库的性能下降
- Mybatis的优点
- 可以由开发人员自己优化核心SQL
- sql和java编码分开,功能边界清晰.一个专注于业务,一个专注于数据
Mybatis的下载地址
- https://github.com/mybatis/mybatis-3
Mybatis初步使用
- 创建实体类
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", gender='" + gender + '\'' +
'}';
}
}
- 创建Mybatis的全局配置文件
- 全局配置文件包含了Mybatis运行环境信息,包括数据库的连接信息、事务信息、数据库连接池信息等等,指导这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>
<!-- 1. mybatis使用properties标签引入外部properties配置文件内容 -->
<properties></properties>
<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_study?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
- 创建SQL映射文件 ,映射文件的作用就是相当于定义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">
<!--
namespace:名称空间;指定为接口的全类名
id:唯一标识
resultType:返回值类型
#{id}:从传递过来的参数中取出id值
-->
<mapper namespace="com.wjl.mybatis.dao.EmployeeMapper">
<select id="getEmpById" resultType="com.wjl.mybatis.bean.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{id}
</select>
</mapper>
- 测试类的实现
- 不定义接口,直接使用原生的配置文件实现
public void test01() throws IOException { //1. 根据xml配置文件(全局配置文件)创建一个sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 2. 获取SqlSeeion实例,能直接执行已经映射的SQL语句,一个sqlSession代表和数据库的一次会话 SqlSession sqlSession = sqlSessionFactory.openSession(); try{ // 变量1未xml文件执行的方法,变量2为传入的参数 Employee employee = sqlSession.selectOne("com.mybatis.EmployeeMapper.selectEmp",1); System.out.println(employee); }finally { sqlSession.close(); } }
- 使用接口进行实现,定义相应的接口,映射到Xml文件中进行实现,创建的接口名一定要和xml文件名保持一致,方法名对应着xml文件中每一个增删改查的名字
// 定义接口类 public interface EmployeeMapper { public Employee getEmpById(Integer 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 namespace="com.wjl.mybatis.dao.EmployeeMapper"> <!-- id为接口中对应的方法名 resultType:返回值类型 #{id}:从传递过来的参数中取出id值--> <select id="getEmpById" resultType="com.wjl.mybatis.bean.Employee"> select id,last_name lastName,email,gender from tbl_employee where id = #{id} </select> </mapper>
@Test public void test02() throws IOException { //1.获取sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 2. 获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); try { // 3.获取接口的实现类对象 EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = mapper.getEmpById(1); System.out.println(employee); }finally { sqlSession.close(); } }
注释:SqlSession
SqlSession代表和数据库的一次对话,用完必须关闭
SqlSession和connection都是非线程安全.每次使用都应该去获取新的对象
mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象(将接口和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>
<!-- 1. mybatis使用properties标签引入外部properties配置文件内容
resource:引入类路径下的资源
url:引入网络路径或者磁盘路径下的文件
-->
<properties resource="dbconfig.properties"></properties>
<!-- 2. settings 包含很多重要的设置
用来设置每一个设置项
-->
<settings>
<!--开启驼峰命名法,默认false -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 3. typeAliases别名处理器 别名不区分大小写,同时也可以使用@Alias注解在类上为其指定一个别名-->
<typeAliases>
<!--typeAlias:为某个java类型起别名
type:指定要起别名的类型全类名;默认类名就是类名小写;employee
alia:别名
-->
<!-- <typeAlias type="com.mybatis.bean.Employee" alias="emp"></typeAlias>-->
<!--package:为某个包下的所有类批量起别名
name:指定包名(为当钱包以及下面所有后代的包每一个类都起一个默认别名)
-->
<package name="com.mybatis.bean"></package>
<!--批量起别名情况下,使用@Alias注解为某个类型指定新的别名 -->
</typeAliases>
<!-- 4. plugins -->
<!--5. environments ,mybatis可以配置多种环境,default指定使用哪种环境
每一个environment是一个具体的环境信息,通过id来选择环境
必须配置transactionManager:事务管理器(两种:JDBC,MANAGED),也可自定事务管理器,实现TransactionFactory
type表示事务管理器的类型
dataSource:数据源(UNPOOLED|POOLED|JNDI)
可以自定义数据源,实现DataSourceFactory
-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<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>
</environment>
</environments>
<!-- 6.databaseIdProvider标签 支持多数据厂商
DB_VENDOR 作用就是得到数据库厂商的标识,mybatis就能根据数据库厂商标识执行不同的sql
MySQL,Oracle,SQL Server ,xxxx
在Mapper文件中通过databaseid来指定数据
-->
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"></property>
<property name="Oracle" value="oracle"></property>
</databaseIdProvider>
<!-- 7. mappers标签 将sql映射注册到全局配置中 -->
<mappers>
<!--mapper :注册一个sql映射
resource:引用类路径下的sql映射文件
url:引用网络或者磁盘路径下的资源
class:引用(注册接口)
1.有sql映射文件,映射文件必须与接口同名.并且放到同一个目录下
2.没有sql文件,所有的sql都是利用注释写在接口上
-->
<mapper resource="EmployeeMapper.xml"/>
<!-- package 批量注册-->
<package name="com.wjl.mybatis.dao"></package>
</mappers>
</configuration>
Mybatis的简单的增删改查
Mybatis的参数传递
- 单个参数
- 可以接受基本类型,对象类型,集合类型的值,这种情况Mybatis可以直接使用这个参数,不需要进行任何处理
- 多个参数
- 任意多个参数时,会被Mybatis包装成一个Map传入,Map的Key是param1,param2…,值就是参数的值
- 为参数赋值,可以使用@Param为参数起一个名字,这样使用时key就是我们自己指定的名字
public Employee getEmpById(@Param("id") Integer id);
- 命名参数
- 当这些参数属于我们业务POJO时,我们直接传递POJO
- Map
- 可以直接封装多个参数为map,直接传递
参数的获取
-
#{} 是以预编译的形式,将参数设置到sql中,防止sql注入
-
${}取出参数值直接拼装在sql语句中,会有安全问题
-
大多情况下,我们取参数的值都应该区使用#{}
#{}取值时的用法
- 可以指定一个特殊的数据类型(jdbcType、javaType,mode(存储过程),numericScale)
- jdbcType通常需要在某种特定 条件下被设置:在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理.比如Oracle(报错)
- 全局配置中,jdbcTypeForNull=OTHER,对Orcale无效,可以将jdbcTypeForNull=NULL(在settings标签中)
- JdbcType Other:无效的类型,因为mybatis对所有null都映射的是原声JDBC Other,Oracle不能正确处理,应该设置为jdbcType=Null
Mybatis的增加操作
-
简单的增加,返回值可以是int类型代表插入多少行,也可以是boolean代表插入成功失败
<insert id="addEmp" parameterType="com.mybatis.bean.Employee" > insert into tbl_employee(last_name,email,gender) values(#{lastName},#{email},#{gender}) </insert>
-
获取生成的主键id
- 如果数据库支持自动生成主键(MySQL SQLServer等)则可以设置useGeneratedKeys=“true”,使用自增主键获取主键值策略,keyProperty:指定对应的主键值,也就是mybatis获取到主键值以后,将这个值封装给JavaBean的哪个属性
<insert id="addEmp" parameterType="com.mybatis.bean.Employee" useGeneratedKeys="true" keyProperty="id"> insert into tbl_employee(last_name,email,gender) values(#{lastName},#{email},#{gender}) </insert>
- 如果数据库不支持自增型主键(比如Oracle),可以使用selectKey子元素,select元素将会首先运行,id会被设置 ,然后插入语句会被调用
<!--Orcale不支持自增,Orcale使用序列来模拟自增 --> <insert id="addEmp" databaseId="orcale"> <!--keyProperty:查出的主键值封装给javaBean的哪个属性 order="BEFORE" :当前sql在插入sql之前运行 resultType 查出的返回值类型 运行顺序: 先运行selelctKey查询id的属性,查出id值封装给javaBean的id属性 在运行插入的sql,就可以取出id的对应值 --> <selectKey keyProperty="id" order="BEFORE" resultType="integer"> select EMPLOYEES_SEQ.nextval from dual </selectKey> <!--插入的主键使用序列中拿到的 --> insert into tbl_employee(id,last_name,email,gender) values(#{id},#{lastName},#{email},#{gender}) <!-- order="AFTER" 方式 <selectKey keyProperty="id" order="AFTER" resultType="integer"> select EMPLOYEES_SEQ.currval from dual </selectKey> insert into tbl_employee(id,last_name,email,gender) values(EMPLOYEE_SEQ.nextval,#{lastName},#{email},#{gender}) --> </insert>
更新操作
- 同样,上述的获取主键的方法同样适用于更新操作,id需要和方法名保持一致
<update id="updateEmp">
update tbl_employee set last_name =#{lastName},email=#{email},gender=#{gender}
where id = #{id}
</update>
查询操作
- select元素用来定义查询操作
- id为唯一标识符,用来引用这条语句,需要和接口的方法名保持一致
- parameterType:参数类型,可以不传,Mybatis会根据TypeHandler自动推断
- resultType:返回值类型,别名或者全类名,如果是集合定义集合中元素的类型,不能喝resultMap一起使用
<select id="getEmpById" resultType="com.mybatis.bean.Employee"> select id,last_name,email,gender from tbl_employee where id = #{id} </select>
- resultMap,自定义某个JavaBean的封装规则,type 自定义规则的java类型, id:唯一id方便引用,在查询操作中可以指定resultMap来进行封装后的缓存
<resultMap id="myEmp" type="com.wjl.mybatis.bean.Employee"> <!--指定主键列的封装规则 id定义主键会有底层优化 column:指定哪一列 property:指定对应的javabean属性 --> <id column="id" property="id"></id> <result column="last_name" property="lastName"></result> <result column="email" property="email"></result> <result column="gender" property="gender"></result> <!-- 其他不指定的列会自动封装,我们只要写resultMap就把全部的映射规则全都写上--> </resultMap>
- 测试resultMap
<!--resultMap :自定义结果集规则映射 --> <select id="getEmployeeById" resultMap="myEmp"> select * from tbl_employee where id = #{id} </select>
resultMap的使用
-
新建实体类Department
package com.wjl.mybatis.bean; import java.util.List; public class Department { private Integer id; private String departmentName; private List<Employee> emps; public List<Employee> getEmps() { return emps; } public void setEmps(List<Employee> emps) { this.emps = emps; } public Integer getId() { return id; } @Override public String toString() { return "Department{" + "id=" + id + ", departmentName='" + departmentName + '\'' + '}'; } public void setId(Integer id) { this.id = id; } public String getDepartmentName() { return departmentName; } public void setDepartmentName(String departmentName) { this.departmentName = departmentName; } }
-
在Employee中增加Department属性
public class Employee { private Integer id; private String lastName; private String email; private String gender; public Department department; }
-
数据库中Employee增加Department id
-
查询与员工对应的部门和id
- 级联查询:级联属性封装结果集 ,利用resultMap将查询出来的deplement封装进属性
<!--查询员工对应的部门,通过sql直接查询出对应的部门的名称与id --> <!--public Employee geeEmployeeAndDept(Integer id); --> <select id="geeEmployeeAndDept" resultMap="myEmpPlus"> select e.id id , e.last_name last_name , e.gender gender ,e.d_id d_id ,d.id did ,d.dept_name dept_name from tbl_employee e,tbl_department d where e.d_id = d.id and e.id = #{id} </select>
<!--级联查询:级联属性封装结果集 --> <resultMap id="myEmpPlus" type="com.wjl.mybatis.bean.Employee"> <id column="id" property="id"></id> <result column="last_name" property="lastName"></result> <result column="email" property="email"></result> <result column="gender" property="gender"></result> <result column="did" property="department.id"></result> <result column="dept_name" property="department.departmentName"></result> </resultMap>
- 使用association定义关联单个对象的封装规则,将查询出的depeid与名字封装成department对象
<!--使用association定义关联单个对象的封装规则 --> <resultMap id="myEmpPlus2" type="com.wjl.mybatis.bean.Employee"> <id column="id" property="id"></id> <result column="last_name" property="lastName"></result> <result column="email" property="email"></result> <result column="gender" property="gender"></result> <!-- association 用来指定可以联合的javaBean对象 property="department":指定哪个属性可以是联合的对象 javaType="" 指定这个属性对象的类型 --> <association property="department" javaType="com.wjl.mybatis.bean.Department"> <id column="did" property="id"></id> <result column="dept_name" property="departmentName"></result> </association> </resultMap>
- 使用association进行分步查询,先按照员工id查询出员工信息 根据查出ID员工信息中d_id 值去部门表查出部门信息,再将部门设置到员工中
<resultMap id="myEmpPlus3" type="com.wjl.mybatis.bean.Employee"> <id column="id" property="id"></id> <result column="last_name" property="lastName"></result> <result column="email" property="email"></result> <result column="gender" property="gender"></result> <!-- association 定义关联对象的封装规则 select: 表明当前属性是调用select方法查询出的结果 column : 指定将哪一列的值传给这个方法 流程:使用select指定的方法(闯入cloumn指定的这列参数的值)查出对象,封装给对象 --> <association property="department" select="com.wjl.mybatis.dao.DepartmentMapper.getDeptById" column="d_id"> </association> </resultMap>
<!-- 首先查询员工的详细信息--> <select id="getEmpByIdStep" resultMap="myEmpPlus4"> select * from tbl_employee where id = #{id} </select>
<!-- 根据查询出来的depid查询部门信息--> <select id="getDeptById" resultType="com.wjl.mybatis.bean.Department"> select id,dept_name departmentName from tbl_department where id = #{id} </select>
这样分段查询时会将sql一起执行,全部查询出来,但有时候我们不需要查询出部门信息,只有需要
的时候再进行查询,可以设置懒加载进行查询,全局懒加载开启后,可以在查询的 association 标签上设置fetchType=“lazy” (collection开启懒加载也是如此):表示使用延迟加载(需要全局开启懒加载)<settings> <!-- 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 --> <setting name="lazyLoadingEnabled" value="true"></setting> <!--当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载. --> <setting name="aggressiveLazyLoading" value="false"></setting> </settings>
-
情景二:查询部门时将所该部门下的员工查询出来
- collection嵌套结果集的方式,定义关联的集合类型元素的封装规则 ,在查询部门的时候将员工的信息查询出来,在使用resultMap中的collection进行封装结果集
<select id="getDeptByIdPlus" resultMap="mydept"> select d.id did, d.dept_name dept_name,e.id eid,e.last_name last_name ,e.email email ,e.gender gender from tbl_department d left join tbl_employee e on d.id = e.d_id where d.id = #{id} </select>
<resultMap id="mydept" type="com.wjl.mybatis.bean.Department"> <id column="did" property="id"></id> <result column="dept_name" property="departmentName"></result> <!--定义关联的集合类型的属性封装规则 ofType:指定集合中的元素的类型 --> <collection property="emps" ofType="com.wjl.mybatis.bean.Employee"> <!--定义集合中元素的封装规则 --> <id column="eid" property="id"></id> <result column="last_name" property="lastName"></result> <result column="email" property="email"></result> <result column="gender" property="gender"></result> </collection> </resultMap>
- 分步查询,如上述的分步查询,首先查询出部门的id,再根据部门id去查找员工
<resultMap id="mydeptstep" type="com.wjl.mybatis.bean.Department"> <id column="id" property="id"></id> <result column="dept_name" property="departmentName"></result> <collection property="emps" select="com.wjl.mybatis.dao.EmployeeMapperPlus.getEmpsByDepid" column="id" fetchType="lazy" > </collection> </resultMap>
扩展:将多列的值传递过去
将多列的值封装map传递,column="{key=column1,key2=column2}"
fetchType=“lazy” :表示使用延迟加载(需要全局开启懒加载 -lazy:延迟加载 -eager :立即
删除操作
- 使用delete标签进行删除操作
<delete id="deleteEmpById">
delete from tbl_employee where id = #{id}
</delete>