170903-关于MyBatis
MyBatis总体介绍: MyBatis实际上是Ibatis3.0版本以后的持久化层框架【也就是和数据库打交道的框架】!
和数据库打交道的技术有:
原生的JDBC技术---》Spring的JdbcTemplate技术 这些工具都是提供简单的SQL语句的执行,但是和我们这里学的MyBatis框架还有些不同, 框架是一整套的东西,例如事务控制,查询缓存,字段映射等等。
我们用原生JDBC操作数据库的时候都会经过: 编写sql---->预编译---->设置参数----->执行sql------->封装结果 我们之所以不使用原生的JDBC工具,是因为这些工具:
1.功能简单,sql语句编写在java代码里面【一旦修改sql,就需要将java及sql都要重新编译!】这属于硬编码高耦合的方式。
2.我们希望有开发人员自己编写SQL语句,并且希望SQL语句与java代码分离,将SQL语句编写在xml配置文件中,实现数据表中记录到对象之间的映射!
sql和java编码分开,功能边界清晰,一个专注于业务,一个专注于数据,可以使用简单的 XML或注解用于配置和原始映射,将接口和Java的POJO映射成数据库中的记录,完成 业务+底层数据库的媒介!
1.MyBatis历史
原是Apache的一个开源项目iBatis, 2010年6月这 个项目由Apache Software Foundation 迁移 到了 Google Code,随着开发团队转投Google Code 旗下, iBatis3.x正式更名为MyBatis ,代码于 2013年11月迁移到Github(下载地址见后)。
iBatis一词来源于“internet”和“abatis”的组合,是 一个基于Java的持久层框架。 iBatis提供的持久 层框架包括SQL Maps和Data Access Objects、(DAO)
2.MyBatis简介
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis可以使用简单的XML或注解用于配置和原 始映射,将接口和
Java的POJO(Plain Old JavaObjects,普通的Java对象)映射成数据库中的记录.
3.为什么要使用MyBatis?
MyBatis是一个半自动化的轻量级的持久化层框架。
JDBC
– SQL夹在Java代码块里,耦合度高导致硬编码内伤
– 维护不易且实际开发需求中sql是有变化,频繁修改的情况多见 Hibernate和JPA
– 长难复杂SQL,对于Hibernate而言处理也不容易
– 内部自动生产的SQL,不容易做特殊优化。
– 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。 导致数据库性能下降。
对开发人员而言,核心sql还是需要自己优化,sql和java编码分开,功能边界清晰,一个专注业务、 一个专注数据。
4.去哪里找MyBatis?
https://github.com/mybatis/mybatis-3/ 或者在百度直接搜索mybatis,然后找到github下的地址下载即可!
5.为了快速写一个MyBatis的HelloWorld,我们先将数据表建立起来
1.创建数据库及数据表
CREATE DATABASE mytabis;
CREATE TABLE tbl_employee(
id INT(11) PRIMARY KEY AUTO_INCREMENT,
last_name VARCHAR(255),
gender CHAR(1),
email VARCHAR(255) )
然后插入两条数据;
2.创建一个动态WEB工程,然后创建与上述数据表对应的实体类:
3.[参考mybatis官方文档]
加入需要的jar包[mybatis所需要的jar包,
mybatis-3.4.1.jar
和数据库打交道的jar包,
mysql-connector-java-5.1.37-bin.jar
以及看打印日志所需要的log4j的jar包]:
log4j-1.2.17.jar
//当然需要注意的是:log4j的jar包是需要log4j.xml文件的
4.创建mytabis-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 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/mytabis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 将我们写好的sql映射文件一定要注册到全局配置文件中 -->
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
5.创建测试用例,.复制mybatis官方文档代码,代码如下:
public class MyBatisTest {
@Test
public void test() throws IOException {
String resource = "mytabis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession =null;
try{
//2.获取sqlSession实例,能直接执行已经映射的SQL语句
sqlSession= sqlSessionFactory.openSession(); //需要两个参数,第一个参数是sql语句的唯一标识, //第二个参数是执行sql要用的参数
Employee employee = sqlSession.selectOne("com.neuedu.mybatis.EmployeeMapper.selectEmp",1);
System.out.println(employee);
}catch(Exception e){
}finally{
sqlSession.close();
}
}
}
6. 创建sql语句的映射文件EmployeeMapper.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.neuedu.mybatis.EmployeeMapper">
<!-- namespace:名称空间 id:sql语句的唯一标识 resultType:返回值类型 #{id}:接收参数传递过来的id值 -->
<select id="selectEmp" resultType="com.neuedu.mybatis.bean.Employee">
select id,last_name lastName,gender,email from tbl_employee where id = #{id}
</select>
</mapper>
7.总结: HelloWorld简单版
– 创建一张测试表
–创建对应的javaBean
–创建mybatis配置文件,sql映射文件
– 测试
1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象 有数据源的一些运行环境信息
2.sql映射文件,配置了每一个sql,以及sql的封装规则等。
3.将sql映射文件注册在全局配置文件中
4.写代码:
1)根据全局配置文件得到SqlSessionFactory
2)使用sqlSession工厂,获取到sqlSession对象使用它来执行增删改查! sqlSession就是代表和数据库的一次会话!用完要关闭!
3)使用sql的唯一标识告诉MyBatis执行哪个sql。而sql都是保存在sql映射文件中的。上面那种开发方式适合老版本的mybatis使用者的开发方式!而新一批的mybatis使用者都是使用接口的方式
8.HelloWorld-接口式编程
– 创建一个Dao接口
– 修改Mapper文件
– 测试
以前都是需要为接口写一个实现类,这是以前,但是此时,mybatis提供了接口可以与sql配置文件动态绑定! 那如何将两者进行绑定呢?
以前sql配置文件的namespace可以随便写,现在就不能随便写了,需呀指定为接口的全限定名! 然后此时接口和sql配置文件做了绑定,然后还要将select标签的id和方法名进行绑定,
总结:
1.接口式编程
原生: Dao ==================> DaoImpl
mybatis: xxMapper ================> xxMapper.xml
2.SqlSession代表和数据库的一次会话,用完必须关闭。
3.SqlSession和Connection一样,都是非线程安全的,每次使用都是应该去获取新的对象,不要将这个对象定义在类变量中使用!
4.mapper接口没有实现类,但是mybatis这个接口生成一个代理对象
<!--将接口与xml文件进行绑定 -->
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
5.两个重要的配置文件
mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等..系统运行环境信息。
sql映射文件:保存了每一个sql语句的映射信息。
三、MyBatis-全局配置文件
MyBatis 的配置文件包含了影响 MyBatis 行为甚深的 设置(settings)和属性(properties)信息。文档的 顶层结构如下:
configuration 配置 properties 属性 settings 设置 typeAliases 类型命名 typeHandlers 类型处理器 objectFactory 对象工厂 plugins 插件 environments 环境 environment 环境变量 transactionManager 事务管理器 dataSource 数据源 databaseIdProvider 数据库厂商标识 mappers 映射器
1.为全局配置文件绑定dtd约束:
1)联网会自动绑定
2)没网的时候【/org/apache/ibatis/builder/xml/mybatis-3-config.dtd】:解压mybatis 的jar包然后在eclipse中绑定
2. properties属性
<configuration>
<!-- 1.mybatis可以使用properties来引入外部properties配置文件的内容resource:引入类路径下的资源 url:引入网络路径或者磁盘路径下的资源 -->
<properties resource="jdbc.properties"></properties>
<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.user}"/>
<property name="password" value="${jdbc.passowrd}"/>
</dataSource>
</environment>
</environments>
<!-- 将我们写好的sql映射文件一定要注册到全局配置文件中 -->
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
3.settings包含很多重要的设置项
setting:用来设置每一个设置
name:设置项名
value:设置项取值
举例:驼峰式命名
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
1).mapUnderscoreToCamelCase:自动完成数据表标准列名和持久化类标准属性名之间的映射。
例如:last_name和lastName
4.typeAliases:【不建议大家使用别名】
作用:A type alias is simply a shorter name for a Java type
<!-- typeAliases:别名处理器,可以为我们的java类型起别名,别名不区分大小写 -->
<typeAliases>
<!-- typeAlias:为某个java类型起别名 type:指定要起别名的类型全类名;默认别名就是类名小写; alias:执行新的别名 -->
<typeAlias type="com.neuedu.mybatis.bean.Employee"/>
<!-- package:为某个包下的所有类批量起别名 name:指定包名(为当前包以及下面所有的后代包的每一个类都起一个默认别名【类名小写】) -->
<package name="com.neuedu.mybatis.bean"/>
<!-- 批量起别名的情况下,使用@Alias注解为某个类型指定新的别名 -->
</typeAliases>
虽然有这么多的别名可以使用:但是建议大家还是使用全类名,看SQL语句是怎么被封装为JAVA 对象的时候简单!
5.typeHandlers:类型处理器 类型处理器:负责如何将数据库的类型和java对象类型之间转换的工具类
6.environments【用于配置MyBatis的开发环境】
<!-- environments:环境们,mybatis可以配置多种环境,default指定使用某种环境。可以达到快速切换环境。
environment:配置一个具体的环境信息;必须有两个标签;id代表当前环境的唯一标识
transactionManager:事务管理器
type:事务管理器的类型;
type="[JDBC|MANAGED]"),这两个都是别名,在Configuration类中可以查看具体类!但是Spring对事务的控制才是最终的管理方案!
JDBC:这个配置就是直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务。
MANAGED:这个配置几乎没做什么,它从来不提交和回滚一个连接。而是让容器来管理事务的整个生命周期。
所以综上:这里如果要配置事务处理器,就配置为JDBC。表示使用本地的JDBC事务。
当然也可以自定义事务管理器:只需要和人家一样实现TransactionFactory接口,type指定为全类名。
dataSource:数据源
type:type="[UNPOOLED|POOLED|JNDI]"
unpooled:无数据库连接池
pooled:有数据库连接池
JNDI:
自定义数据源:实现DataSourceFactory接口,type也是全类名
但是我们也说了,无论是事务管理器的配置还是数据源的配置我们都会使
用spring来做,这里只需要了解一下即可! -->
<environments default="development">
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.passowrd}"/>
</dataSource>
</environment>
<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.user}"/>
<property name="password" value="${jdbc.passowrd}"/>
</dataSource>
</environment>
</environments>
补充:这里可以了解一下如何配置第三方数据源:
定制自己的第三方数据源,如下所示:
要求:需要自定义一个类继承UnpooledDataSourceFactory类,然后在构造器中初始化该对应的dataSource属性,如下所示:
自定义类为:
public class C3P0DataSource extends UnpooledDataSourceFactory{
public C3P0DataSource() {
this.dataSource = new ComboPooledDataSource();
}
}
<!-- 配置使用第三方的数据源。属性需要配置为第三方数据源的属性 -->
<dataSource type="com.neuedu.mapper.C3P0DataSource">
当然这里需要加入c3p0的jar包; c3p0-0.9.2.1.jar mchange-commons-java-0.2.3.4.jar
7.MyBatis的增删改查标签:
<select></select>
<insert></insert>
<update></update>
<delete></delete>
例如:
<insert id="saveEmployee">
INSERT INTO tbl_employee(user_name,email,gender) VALUES(#{userName},#{email},#{gender})
</insert>
8.databaseIdProvider环境
MyBatis is able to execute different statements depending on your database vendor. The ?
MyBatis 可以根据不同的数据库厂商执行不同的语句
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="" />
<property name="MySQL" value="" />
</databaseIdProvider>
Type: DB_VENDOR
–使用MyBatis提供的VendorDatabaseIdProvider解析数据库 厂商标识。也可以实现DatabaseIdProvider接口来自定义。
Property-name:数据库厂商标识
Property-value:为标识起一个别名,方便SQL语句使用
这样在执行不同数据库的时候,就会执行不同数据库的语句了!
当然如上所示:当有指定了databaseId属性的和没有指定databaseId属性的,都有的情况下那就按着有指定databaseId属性的sql语句执行!
所以:databaseId属性:
1).对于不同的数据库,做不同的SQL操作时,SQL语句会有不同。例如:
MySQL和Oracle的分页[mysql分页为limit,而ORACLE分页我们使用rownumber]、插入主键的方式。
MySQL:INSERT INTO tbl_employee(user_name,email,gender) VALUES(#{userName},#{email},#{gender})
ORACLE:INSERT INTO employee(id,user_name,email) VALUES(test.seq.nextval,#{userName},#{email})
所以要想使用ORACLE的自增序列还需要创建一个序列:
如下所示:
create sequence test_seq start with 1;
这样做的好处:在service层只需要调用一个mybatis的方法,而不需要关注底层选择使用的数据库。
employeeDao.insert(employee);
那么:mybatis如何区分使用的是哪个数据库呢?使用databaseId属性
①.在mybatis-config.xml文件中进行配置
<databaseIdProvider type="DB_VENDOR">
<property name="ORACLE" value="oracle"/>
<property name="MySQL" value="mysql"/>
</databaseIdProvider>
②.在mybatis的映射文件中使用databaseId来区分使用的是哪一个数据库
在mybatis的全局配置文件配置了这个之后,我们只需要在sql映射文件中通过在执行语句的标签上加一个属性databaseId即可!
<select id="getEmployeeById" resultType="emp">
select * from tbl_employee where id = #{id}
</select>
<select id="getEmployeeById" resultType="emp" databaseId="mysql">
select * from tbl_employee where id = #{id}
</select>
<select id="getEmployeeById" resultType="emp" databaseId="oracle">
select * from tbl_employee where id = #{id}
</select>
9.mapper映射
<!-- mappers:将sql映射注册到全局配置中 -->
<mappers>
<!-- mapper:注册一个sql映射
注册配置文件:
resource:引用类路径下的sql映射文件
mybatis/mapper/EmployeeMapper.xml
url:引用网络路径下或者磁盘路径下的sql映射文件
url="file:///var/mappers/AuthorMapper.xml"
注册接口
class:引用(注册)接口
1.有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一个目录下;
2.没有sql映射文件,所有的sql都是利用注解写在接口上;
推荐:
比较重要的,复杂的Dao接口我们来写sql映射文件
不重要,见到的Dao接口为了开发快速可以使用注解
-->
<mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
<mapper class="com.neuedu.mybatis.mapper.EmployeeMapperAnnotation"/>
<!-- 批量注册:
1.注解版肯定是没问题的
2.但是对于Mapper.xml映射文件和接口分开的,就需要保证在同一个包下了,否则找不到 -->
<package name="com.neuedu.mybatis.mapper"/>
</mappers>
四、MyBatis有基于XML文件的方式也有基于注解的方式,如下所示:
1.使用基于注解的MyBatis映射
1).新建一个XxxMapper接口。
通常情况下,Mapper接口和Mapper.xml文件同名,且在同一个包下;
MyBatis在加载Mapper接口的时候也会自动的加载Mapper.xml文件。
2).在该接口中新建方法.
若该方法上没有注解,则mybatis会去对应的Mapper.xml文件中查找和方法名匹配的id的节点进而执行该节点对应的SQL语句。
3).在方法上使用MyBatis的注解
如下所示:代码和配置如下所示:
Mapper接口为:
public interface EmployeeMapper {
public Employee getEmployeeById(Integer id);
//讲到此处了!此处以下没讲
@Update("update tbl_employee set user_name = #{userName} where id = #{id}")
public void updateEmployee(Employee employee);
}
MyBatis的sql映射文件可以没有,MyBatis的全局映射文件可以只用mapper标签加载Mapper接口
<mappers>
<!-- resource="com/neuedu/mapper/EmployeeMapper.xml" -->
<mapper class="com.neuedu.mapper.EmployeeMapper"/>
</mappers>
2.获取自增主键值【当向数据库中插入一条数据的时候,默认是拿不到主键的值的, 需要设置如下两个属性才可以拿到主键值!】
对于mysql:
<!--设置userGeneratedKeys属性值为true:使用自动增长的主键。使用keyProperty设置把主键值设置给哪一个属性-->
<insert id="addEmp" parameterType="com.neuedu.mybatis.bean.Employee" useGeneratedKeys="true" keyProperty="id" databaseId="mysql">
insert into tbl_employee(last_name,email,gender)
values(#{lastName},#{gender},#{email})
</insert>
对于ORACLE: (自己没试)
<insert id="addEmp" databaseId="oracle">
<!-- order="BEFORE" 设置selectKey中包含的语句先执行。且返回的类型为resultType指定的类型。
再把该值付给keyProperty指定的列 -->
<selectKey keyProperty="" resultType="int" order="BEFORE"></selectKey>
insert into tbl_employee(id,last_name,email,gender)
values(#{id},#{lastName},#{gender},#{email})
</insert>
3.SQL节点:
1).可以用于存储被重用的SQL片段
2).在sql映射文件中,具体使用方式如下:
<insert id="addEmp">
insert into tbl_employee(id, <include refid="employeeColumns"></include>)
values(#{id},#{lastName},#{gender},#{email})
</insert>
<sql id="employeeColumns">
last_name,email,gender
</sql>
五:参数处理
单个参数:mybatis不会做特殊处理
#{参数名}: 取出参数值
多个参数:mybatis会做特殊处理
多个参数会被封装成一个map,
key:param1...paramN,或者key:0,1,2,3参数的索引也可以
value:传入的参数值
#{}就是从map中获取指定的key的值
异常:
org.apache.ibatis.binding.BingdingException:
Parameter 'id' not found.
Available parameters are [1,0,param1,param2]
操作:
方法:public Employee getEmployeeAndLastName(Integer id,String lastName);
取值:#{id},#{lastName}
命名参数:明确指定封装参数时map的key:@param("id")
多个参数会被封装成一个map,
key:使用@Param注解指定的值
value:参数值 #{指定的key}取出对应的参数值
POJO: 如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;
#{属性名}:取出传入的pojo的属性值
Map: 如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
#{key}:取出map中对应的值 List、Set #这里面的值也会被封装到map集合中:
key:collection 值:对应的参数值
#{collection[0]}或#{list[0]}
#关于参数的问题:
①.使用#{}来传递参数
②.若目标方法的参数类型为对象类型,则调用其对应的getter方法,如getEmail()
③.若目标方法的参数类型为Map类型,则调用其get(key)
④.若参数是单个的,或者列表,需要使用@param注解来进行标记
⑤.注意:若只有一个参数,则可以省略@param注解
六、参数值的获取
#{}:可以获取map中的值或者pojo对象属性的值
${}: 可以获取map中的值获取pojo对象属性的值
案例演示:select * from tbl_employee where id = ${id} and last_name = #{lastName}
preparing:select * from tbl_employee where id = 2 and last_name = ?
区别:
#{}:是以预编译的形式,将参数设置到sql语句中PreparedStatement;防止sql注入
${}:取出的值直接拼装在sql语句中,会有安全问题;
大多情况下,我们取参数的值都应该去使用
#{};原生JDBC不支持占位符的地方我们就可以使用
${}进行取值,
#{}只是取出参数中的值!
在某些情况下,比如分表、排序;
按照年份分表拆分select * from ${year}_salary where xxx;
[表名不支持预编译]
select * from tbl_employee order by ${f_name}
${order} :排序是不支持预编译的!
演示:select元素
Select元素来定义查询操作。
Id:唯一标识符。
– 用来引用这条语句,需要和接口的方法名一致
parameterType:参数类型。
–可以不传,MyBatis会根据TypeHandler自动推断
resultType:返回值类型。
– 别名或者全类名,如果返回的是集合,定义集合中元素的类型。不能和resultMap同时使用
1.返回类型为一个List
eg:public List<Employee> getEmpsByLastNameLike(String lastName);
<!-- resultType:如果返回的是一个集合,要写集合中元素的类型 -->
<selecct id="getEmpsByLastNameLike" resultType="com.neuedu.mybatis.bean.Employee">
select * from tbl_employee where lastName like #{lastName}
</select>
2.返回单条记录为一个Map
<!-- 返回一条记录的map:key就是列名,值就是对应的值 -->
public Map<String,Object> getEmpByIdReturnMap(Integer id);
<select id="getEmpByIdReturnMap" resultType="map">
select * from tbl_employee where id = #{id}
</select>
3.返回为一个ResultMap:
自定义结果集映射规则
尤其是当数据表的列名和类的属性名不对应的时候:
1.起别名
2.符合下划线转驼峰式命名规范
3.用这里的resultMap<!-- resultMap:自定义结果集映射规则 -->
public Employee getEmployeById(Integer id);
<!-- 自定义某个javaBean的封装规则
type:自定义规则的javabean类型 id:唯一id方便引用 -->
<resultMap type="com.neuedu.mybatis.bean.Employee" id = "myEmp">
<!-- 指定主键列的封装规则 id定义主键列会有底层优化 column:指定是哪一列 property:指定对应的javaBean属性 -->
<id column="id" property = "id">
<!-- 定义普通列封装规则 -->
<result column="last_name" property="lastName"/>
<!--其它不指定的列只要属性名和列名会自动封装,我们只要写resultMap就把全部的映射规则都写上,不写是因为列名和属性名是对应的 -->
</resultMap>
<!--只需要在映射的地方映射一下就可以了 --》
<select id="getEmployeById" resultMap="myEmp">
select * from tbl_employee where id = #{}
</select>