自我控制是最强者的本能。——萧伯纳
目录
Mybatis简介
什么是Mybatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
也就是说MyBatis通过一些配置后,只需要关注SQL语句即可,而不用花费大量的时间精力去编写一些复杂的重复的代码,如:注册驱动,获取连接,获取传输器,处理结果集等等。这一切得复杂的代码都被MyBatis给封装集成了,大大的减少了开发过程中的代码量与出错的概率。
总结:Mybatis对JDBC访问数据库的过程进行了封装,简化了JDBC代码,解决JDBC将结果集封装为Java对象的麻烦。
为什么使用Mybatis
Mybatis对JDBC访问数据库的过程进行了封装,那么在不使用Mybatis的情况下,JDBC是如何的呢?
使用传统方法访问数据库
(1)使用JDBC访问数据库有大量重复代码(比如注册驱动、获取连接、获取传输器、释放资源等);
(2)JDBC自身没有连接池,会频繁的创建连接和关闭连接,效率低;
(3)SQL是写死在程序中,一旦修改SQL,需要对类重新编译;
(4)对查询SQL执行后返回的ResultSet对象,需要手动处理,有时会特别麻烦;
使用Mybatis
(1)Mybatis对JDBC对了封装,可以简化JDBC代码;
(2)Mybatis自身支持连接池(也可以配置其他的连接池),因此可以提高程序的效率;
(3)Mybatis是将SQL配置在mapper文件中,修改SQL只是修改配置文件,类不需要重新编译。
(4)对查询SQL执行后返回的ResultSet对象,Mybatis会帮我们处理,转换成Java对象。
由上面可以看出,JDBC中的问题(复杂的代码,连接的创建,结果集的处理)几乎都被Mybatis解决了。
Mybatis的架构
(1)mybatis-config.xml是Mybatis的核心配置文件,通过其中的配置可以生成SqlSessionFactory,也就是SqlSession工厂
(2)基于SqlSessionFactory可以生成SqlSession对象
(3)SqlSession是一个既可以发送SQL去执行,并返回结果,类似于JDBC中的Connection对象,也是Mybatis中至关重要的一个对象。
(4)Executor是SqlSession底层的对象,用于执行SQL语句
(5)MapperStatement对象也是SqlSession底层的对象,用于接收输入映射(SQL语句中的参数),以及做输出映射(即将SQL查询的结果映射成相应的结果)
了解了这些,那就往下看看Mybatis如何使用吧。
Mybatis入门
准备数据
在此呢,以代码为例进行展示
创建数据库、创建表、并插入数据
1、创建数据库
create database if not exists userdb charset utf8;--userdb数据库不存在的话创建,指定编码utf8
use userdb; -- 选择userdb数据库
2、如果存在user表的话删除
drop table if exists user;
3、创建user表
create table user(
id int primary key auto_increment,
name varchar(50),
job varchar(50),
age int
);
4、插入数据
insert into user values(null, '张飞', '匹夫', 22);
insert into user mp values(null, '刘备', '老大', 29);
insert into user values(null, '关羽', '马夫', 24);
insert into user values(null, '赵子龙', '愣头青', 25);
insert into user values(null, '华雄', '小喽啰', 18);
数据都是乱写的,莫要认真哈
创建工程
1、创建Maven的Java工程
2、导入Junit、mysql、mybatis等开发包
<dependencies>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<!-- 整合log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
</dependencies>
3、创建测试类并提供一个方法
编写实体类User
此处需要注意一点:实体类的属性名应该与数据库表的字段名一致,不然会出现错误。
当然表的字段名不推荐使用驼峰,两个字母之间使用下划线连接,而实体类的属性是驼峰规则,此时,就需要有额外的配置使得属性名与字段名相匹配。(这个方法此处就不多说了,百度,不止一种方法)
package com.demo.pojo;
import java.io.Serializable;
/*
* 实体类用于封装数据库中的每一条信息
* 为了防止信息丢失,一般都会实现序列化
*/
public class User implements Serializable{
private static final long serialVersionUID = -2261716295130942575L;
//声明实体类的属性
private Integer id;
private String name;
private String job;
private Integer age;
//生成对应的get set方法
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
//重写toString方法
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", job=" + job + ", age=" + age + "]";
}
}
添加UserMapper.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">
<!--
namespace一般指定为当前文件的所在包路径+文件名(将来是接口名)
在程序中通过[ namespace + id ]定位到执行哪一条SQL语句
-->
<mapper namespace="UserMapper">
<!-- 通过select、insert、update、delete标签声明要执行的SQL -->
<!-- 练习1: 查询user表中的所有员工信息
resultType指定查询的结果将会封装到什么类型中 -->
<select id="findAll" resultType="com.demo.pojo.User">
select * from user
</select>
<!--
resultType:返回值类型,简单类型(例如:Integer,String,Emp等)
如果返回集合(List<Emp>),只需配置集合中的元素类型即可!
-->
</mapper>
mapper标签:跟标签,namespace就相当于是一个路径,一般指向该文件的位置(包路径+文件名)
select标签:指定要执行的SQL语句,还有delete、insert、update,标签上可以声明属性
id:要求不能重复,要通过xxxmapper.xml文件名称+id值来寻找执行的SQL语句
resultType属性:返回值结果类型,当然如果说期望的返回值为集合类型,那么此处只需要写集合中包含的元素的类型即可,像这个例子中,期望类型为list<User>,不用写list,写User的全限定路径即可
resultMap属性:复杂对象结构(如多表关联),当然这个属性还可以用来解决表中下划线字段名与实体类中驼峰属性不匹配的问题
添加mybatis-config.xml文件
1、在src/main/resources目录下创建mybatis-config.xml文件(该文件为mybatis的核心配置文件)
2、配置核心配置文件
<?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">
<!-- MyBatis的全局配置文件 -->
<configuration >
</configuration>
<?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">
<!-- MyBatis的全局配置文件 -->
<configuration>
<!-- 1.配置开发环境(需设置一个默认的环境) -->
<environments default="develop">
<!-- 这里可以配置多个环境,比如develop,test等 -->
<environment id="develop">
<!-- 1.1.配置事务管理方式:JDBC/MANAGED JDBC:将事务交给JDBC管理(推荐) MANAGED:自己管理事务 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 1.2.配置数据源,即连接池方式:JNDI/POOLED/UNPOOLED JNDI:已过时 POOLED:使用连接池(推荐) UNPOOLED:不使用连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/userdb?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 2.加载Mapper配置文件(因mapper文件中配置了要执行的SQL语句) -->
<mappers>
<!-- 注意路径 -->
<mapper resource="UserMapper.xml" />
</mappers>
</configuration>
environments标签:该标签内部可以配置多个environment,每个environment可以有自己的配置,各个environment之间的配置互不影响,但是在运行程序时,在多个environment之间只能有一个在运行。
environment标签:进行各项配置,比如说事务、数据源等。
transactionManager 标签:事务管理配置,mybatis中有两种JDBC和MANAGED
JDBC:有提交和回滚设置,它依赖于从数据源得到的连接来管理事务范围。推荐使用。
MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接。需要自己手动添加并管理。不推荐使用。
DataSource:配置数据源,也就是连接池。数据源类型有三种:JNDI(已过时)/POOLED(使用连接池,mybatis会创建连接,使用完后连接返回池中)/UNPOOLED(不使用连接池)
Mappers标签:导入mapper文件的位置,可以配置多个。
测试
package com.demo.mybatis;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
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 org.junit.Test;
import com.demo.pojo.User;
public class TestMybatis {
/** 练习1(快速入门): 查询user表中的所有员工, 返回一个List<User>集合
* @throws IOException */
@Test
public void findAll() throws IOException {
//1.读取mybatis的核心配置文件(mybatis-config.xml)
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.通过配置信息获取一个SqlSessionFactory工厂对象
SqlSessionFactory fac = new SqlSessionFactoryBuilder().build( in );
//3.通过工厂获取一个SqlSession对象
SqlSession session = fac.openSession();
//4.通过namespace+id找到要执行的sql语句并执行sql语句
List<User> list = session.selectList("UserMapper.findAll");
//5.输出结果
for(User e : list) {
System.out.println( e );
}
}
}
运行结果如下:
增删改查
增删改查大同小异,此处只写一个
新增一个员工信息:貂蝉,舞娘,28
先写UserMapper.xml,如下:
<update id="insert" >
insert into user value(null, '貂蝉', '舞娘', 28)
</update>
测试方法:
@Test
public void testInsert() throws IOException{
//1.读取mybatis的核心配置文件(mybatis-config.xml)
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.通过配置信息获取一个SqlSessionFactory工厂对象
SqlSessionFactory fac = new SqlSessionFactoryBuilder().build( in );
//3.通过工厂获取一个SqlSession对象
SqlSession session = fac.openSession();
//执行sql语句, 返回执行结果
int rows = session.update("UserMapper.insert");
//提交事务
session.commit();
System.out.println("影响的行数: "+rows);
}
删和改与上面一样。
动态SQL
mybatis中有一些元素对SQL语句进行了优化,这里简单的说三个:if、where、foreach
if元素
if元素用于对某一字段进行判断,根据判断的结果决定是否执行包含在其中的SQL片段。
举个栗子:
假如说要查询user表中年龄在最小与最大之间的user信息
年龄最小用minAge表示,最大年龄用maxAge表示
<select id="findAllByAge" resultType="com.demo.pojo.User">
select * from user
where 1=1
<if test="minAge != null">
and age>#{minAge }
</if>
<if test="maxAge != null">
and minAge <![CDATA[ < ]]> #{maxAge}
</if>
</select>
上述代码解释:
1)、where后面为什么会跟个1=1?
如果minAge和maxAge两个参数都没有给赋值的话,代码会变为下面:
<select id="findAllByAge" resultType="com.demo.pojo.User">
select * from user
where 1=1
</select>
看出来了吧,此处的1=1只是为了保证SQL语句不会出错,如果没有1=1的话,SQL就会变为select * from user where,这样where后面没有条件。
2)if元素:如果传入了参数,那么执行if内部的age>#{minAge},或者是age<#{maxAge},又或者两者都执行,此时1=1也并不碍事
3)、有细心的小伙伴是不是已经有发现不一样了呢,对,就是 <![CDATA[ < ]]> 东西,嘿嘿,什么意思呢?
如果你尝试直接写<小于号的话,你会发现xml文件里面报错了。因为小于号(<)在xml文件中是特殊字符,被xml文件当成了标签的开始符号。
解决方法的就是将特殊符号包含在CDATA区()中,这是因为放在CDATA区中的内容,只会被xml解析器当作普通文本来处理。而不是被当成标签的一部分处理。小于号也就变成了 <![CDATA[ < ]]>
如果说让你一直写where 1=1这种东西,烦不烦?或者万一忘了呢?此时你就会用到下面这个元素了where。
where
where元素则用于对包含在其中的SQL语句进行检索,需要时会剔除多余的连接词(比如and或者or),并且在需要时可以添加where关键词
<select id="find" resultType="com.demo.pojo.User">
select * from user
<where>
<if test="id!=null">
id>#{id}
</if>
or 1=2
</where>
</select>
看这个:我要的效果是如果id有值那么就进行查询,如果id没有传值,那么就不进行查询
当id没有传值时:
<select id="find" resultType="com.demo.pojo.User">
select * from user
<where>
or 1=2
</where>
</select>
转换为SQL语句就是:select * from user where 1=2
因为where元素有自动去除多余的and或者or连接词的功能
foreach
如果传过来的参数是集合或者数组的话,那么取参数就要进行遍历了。
<!-- 练习13: 根据员工的id批量删除员工信息
delete from emp
where id in (1,3,5,7)
-->
<update id="deleteByIds">
delete from emp where id in
<foreach collection="array" open="("
item="id" separator="," close=")">
#{id}
</foreach>
</update>
@Test
public void testDeleteByIds() {
Integer[] ids = { 1, 3, 5, 7 };
int rows = session.update(
"EmpMapper.deleteByIds", ids);
session.commit();
System.out.println("影响的行数: "+rows);
}
collection属性:看你传的参数是什么类型,数组就是array,集合就是list
open、close:开始位置,结束位置
separator:分隔符,可以是逗号(,)、or等
item:为封装遍历得到的参数起的名字
#{}和${}占位符
由上面的SQL语句,我们看到很多使用#{}占位符,那么为什么呢?
在JDBC中,有两种传输器Statement和PreparedStatement,前者是将SQL语句写死,会造成SQL的注入攻击,不安全。后者是先写一个骨架,将SQL中写死的值换成了占位符,进行了预编译,不会造成SQL注入。
占位符呢又分为两种#{}和${}。
#{}:当SQL语句中包含的参数值是传递过来的,难么我们会使用#{}占位符进行占位,在SQL执行时,在用传递过来的值替换占位符。其实#{}就是JDBC中的问号(?)占位符,因而为了安全,会对传递的值进行转译处理。
举个栗子:
select * from emp where name=#{name} 相当于是 select * from emp where name=?
${}:如果我们在传递的时候不是一个参数值,而是一个SQL片段呢?此时我们就需要使用${}占位符了,起一个拼接的作用。
select ${columns} from emp
需要注意的是,在传递 ${}对应的值时,需要将值存入map集合中!!
总结:在大多数情况下还是使用#{}占位符,而${}多用于为不带引号的字符串进行占位!
Mapper接口开发
为什么要用mapper接口开发
在上面的过程中,sqlsession想要执行某个SQL方法的话,需要传入一个字符串参数,该值对应的内容为: (Mapper文件中的) namespace + id, 通过这种方式, 找到Mapper文件中映射的SQL语句并执行!!但是此处比较容易发生错误而且还没有错误提示,我们应该去寻找更好的方法,也就是mapper接口开发。
使用mapper接口开发需要注意几点:
1、创建一个接口,接口的全路径名和mapper文件的namespace值要相同
2、mapper文件中每条要执行的SQL语句,在接口中要添加一个对应的方法,并且接口中的方法名和SQL标签上的id值相同
3、Mapper接口中方法接收的参数类型,和mapper.xml中定义的sql的接收的参数类型要相同
4、接口中方法的返回值类型和SQL标签上的resultType即返回值类型相同(如果方法返回值是集合,resultType只需要指定集合中的泛型)
如何使用mapper接口开发
创建一个接口
接口中提供一个方法
package com.demo.mapper;
import java.util.List;
import com.demo.pojo.User;
public interface Usermapper {
List<User> findAll();
}
修改UserMapper.xml文件
<mapper namespace="com.demo.mapper.Usermapper">
此时,namespace的路径应该为UserMapper接口的全限定路径
注意:方法的名字要和映射的sql标签的id值保持一致
方法的返回值类型和resultType的类型要一致
创建测试方法
@Test
public void testFindAll() throws Exception{
......
//3.获取Mapper接口对象
UserMapper map = session.getMapper(UserMapper.class);
//4.调用接口对象的方法进行查询
List<User> list = map.findAll();
//5.输出结果
for (User e : list) {
System.out.println(e);
}
上面的获取sqlsession的方法都一样
优化
可以加入日志配置
在项目中加入log4j的配置文件,用于打印日志信息,便于开发调试。
mybatis默认使用log4j作为输出日志信息。
只要将该文件放在指定的位置,log4j工具会自动到指定位置加载上述文件,读取文件中的配置信息并使用!
在src(或相似的目录)下创建log4j.properties如下:
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
JDBC.properties文件
将连接数据库的配置信息单独放在一个properties文件中(方便管理和维护), 然后在MyBatis的mapper文件中引入properties文件的配置信息
在src目录下创建一个名称为jdbc.properties的文件
jdbc.properties文件内容如下:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/userdb?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
在mybatis-config.xml文件中引入jdbc.properties文件
<properties resource="jdbc.properties"/> 标签用于引入jdbc.properties文件,默认到classpath即类目录下寻找指定的文件;
properties标签上value属性中配置的 ${jdbc.xxx},比如${jdbc.driver}:就是jdbc.properties文件中的 jdbc.driver的值
拓展
mybatis-config文件没有提示
如果说没有外网的情况下,按如下配置:
(1)找到mybatis-3-config.dtd的文件的位置,例如:
(2)复制下面的url地址:
http://mybatis.org/dtd/mybatis-3-config.dtd
(3)菜单栏中: window --> Preferences --> 在搜索框中搜索 [ xml ] XML --> XML Catalog --> User Specified Entries --> Add…
Mapper文件没有提示
(1)找到mybatis-3-mapper.dtd的文件的位置,例如:
(2)复制上面的url地址,即:
http://mybatis.org/dtd/mybatis-3-mapper.dtd
(3)
菜单栏中: window --> Preferences --> 在搜索框中搜索 [ xml ] XML --> XML Catalog --> User Specified Entries --> Add…