MyBatis(一)
MyBatis简介
首先说一下MyBatis是什么?MyBatis就是下图中的鸟,哈哈!
MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设首参数以及获取结果集。MyBatis可以使用简单的XML或注解来配罝和映射原生信息,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
MyBatis和Hibernate一样,是一个ROM框架(Object Relational Mapping,是对象到关系的映射,是一种解决实体对象与关系型数据库相互匹配的技术,它的实现思想就是将数据库中数据表映射成为对象,对关系型数据以对象的形式进行操作。在软件开发中,对象和关系数据是业务实体的两种表现形式,ORM通过使用描述对象和数据库之间映射的元数据,将对象自动持久化到关系数据库中。
MyBatis相对于Hibernate来说更加轻量级,所以MyBatis其实不具备像Hibernate那样自动建表的功能,但是MyBatis现在对我来说足够用了,现在开始记录一下学习MyBatis过程中遇到的问题或者MyBatis的知识点!
从HelloWorld开始
下面从MyBatis的helloworld说起,如何开始学习MyBatis: 首先是MyBatis的官方网站,这里具有最齐全的MyBatis文档,关键是居然有中文版的!?!?http://www.mybatis.org/mybatis-3/zh/d4c0f50720a538647399fc1363896cce.html 另外,这个文档有PDF版本的,可以在网上下载到,如果你希望使用离线版的文档去下载这个PDF即可!
这个是MyBatis的maven依赖,如果没学过maven也不要紧,可以参考我的一片博客 《一文读懂Maven》 ,题目有点夸张,大家将就着看吧!如果你不希望使用maven的话直接导入jar包就好了,MyBatis只有一个jar包(但是不要忘记mysql驱动程序的jar包)!
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
我就演示一下非maven版本的HelloWorld,首先准备一个数据库和表,如图:
这是我整个工程的目录结构:
resource.xml
<?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://127.0.0.1:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="1234" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/xpu/bean/Employee.xml" />
</mappers>
</configuration>
dataSource 标签配置的数据库连接的基本信息,mappers标签配置的是对象映射关系文件,说一下关于引入dtd约束文档的问题,如果未配置dtd约束文档,那么eclipse是没有提示的,你可以在Window—>Preference–>XML—>XMLCatalog里面配置dtd,点击Add,选择URL,填入dtd的URL,选择FileSystem点击打开下载好的dtd文件即可!
接着创建一个JavaBean:
package com.xpu.bean;
public class Employee {
private Integer id;
private String name;
private char gender;
private String address;
public Employee() {
super();
}
public Employee(Integer id, String name, char gender, String address) {
super();
this.id = id;
this.name = name;
this.gender = gender;
this.address = address;
}
public Integer getId() {...}
public void setId(Integer id) {...}
public String getName() {...}
public void setName(String name) {...}
public char getGender() {...}
public void setGender(char gender) {...}
public String getAddress() {...}
public void setAddress(String address) {...}
@Override
public String toString() {...}
}
创建关系映射文件employee.xml,注意namespace必须和JavaBean一一对应,sql查询语句不要写错,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">
<mapper namespace="com.xpu.bean.Employee">
<select id="selOne" parameterType="java.lang.String" resultType="com.xpu.bean.Employee">
select * from employee where id = #{id}
</select>
</mapper>
Test.java
package com.xpu.bean;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Test {
public static void main(String[] args) throws IOException {
//通过resource.xml中的配置信息获取一个SqlSessionFactory对象
Reader reader = Resources.getResourceAsReader("resource.xml");
SqlSessionFactory sqlSessionFty = new SqlSessionFactoryBuilder().build(reader);
//开始一个会话
SqlSession sqlSession = sqlSessionFty.openSession();
//执行查询方法
Employee one = sqlSession.selectOne("com.xpu.bean.Employee.selOne", "1");
System.out.println(one);
//关闭会话
sqlSession.close();
}
}
这便查询到了ID为1的用户,HelloWorld的演示到此结束!
MyBatis的开发模式
1、配置文件
resource.xml
<mappers>
<mapper resource="com/xpu/bean/Employee.xml" />
</mappers>
Employee.xml
<mapper namespace="com.xpu.bean.Employee">
<select id="selOne" parameterType="java.lang.String" resultType="com.xpu.bean.Employee">
select * from employee where id = #{id}
</select>
</mapper>
这样的方式就是我的HelloWorld中所用到的方式,Employee.xml的namespace是我的JavaBean,执行查询的时候需要使用如下方式,selOne是select标签的id,parameterType代表参数的类型,resultType代表返回值的类型,中间是sql查询语句!
//执行查询方法
Employee one = sqlSession.selectOne("com.xpu.bean.Employee.selOne", "1");
2、接口
IEmployeeDao.java
public interface IEmployeeDao {
//查询所有记录
@Select("select * from employee")
public List<Employee> getAll();
}
resource.xml
<mappers>
<mapper class="com.xpu.dao.IEmployeeDao"/>
</mappers>
使用该模式测试:
package com.xpu.dao;
import java.io.IOException;
import java.io.Reader;
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 com.xpu.bean.Employee;
public class Test {
public static void main(String[] args) throws IOException {
Reader reader = Resources.getResourceAsReader("resource.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
//true表示是否自动提交,默认值是false
SqlSession session = factory.openSession(true);
IEmployeeDao mapper = session.getMapper(IEmployeeDao.class);
List<Employee> all = mapper.getAll();
for (Employee employee : all) {
System.out.println(employee);
}
}
}
3、接口+配置文件
这种模式是最常用的模式: Employee.xml:
<mapper namespace="com.xpu.dao.IEmployeeDao">
<select id="getAll" resultType="com.xpu.bean.Employee">
select * from employee
</select>
<select id="getOne" resultType="com.xpu.bean.Employee">
select * from employee where id = #{id}
</select>
</mapper>
resource.xml
<mappers>
<mapper resource="com/xpu/bean/Employee.xml" />
</mappers>
IEmployeeDao.java
package com.xpu.dao;
public interface IEmployeeDao {
public List<Employee> getAll();
public Employee getOne(String id);
}
以上就是MyBatis的三种开发模式,第三种用的比较多,第一种局限性较大,第二种耦合度太强,开发本来的原则就是高内聚、低耦合,使用第二种方式反倒是的耦合度增强不宜使用,所以常用的方式就是第三种开发模式!
下面对需要注意的地方总结一下,参数类型的配置要写全路径名称,避免出错,使用第二种开发模式的时候,需要将接口配置成class,定义返回值类型的时候注意,虽然getAll返回的是一个List集合,但是集合中装的还是对象,所以我们在配置返回值的时候还是需要配置成Employee类:
<mapper class="com.xpu.dao.IEmployeeDao"/>
最后说一个比较重要的问题,为什么我们通过IEmployeeDao.class对象得到的mapper对象是一个实例呢?我们并没有实现接口呀?没有实现接口的话为什么可以拿到他的实现类对象呢?
这是其实是个代理对象,是通过动态代理来实现的,具体参考Proxy类,看看如何通过动态代理生成代理对象,具体可以参考 《Mybatis是如何通过mapper接口生成代理对象的》
MyBatis的CURD
MyBatis最核心的作用还是增删改查嘛,接下来我通过第三种开发模式总结一下MyBatis的CURD,看看Employee.xml中的SQL语句:
<?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.xpu.dao.EmployeeDao">
<select id="selAll" resultType="com.xpu.bean.Employee">
select * from employee
</select>
<select id="selOne" parameterType="java.lang.String" resultType="com.xpu.bean.Employee">
select * from employee where id = #{id}
</select>
<insert id="insertEmp" parameterType="com.xpu.bean.Employee">
insert into employee values(#{id}, #{name}, #{gender}, #{address})
</insert>
<update id="updateEmp" parameterType="com.xpu.bean.Employee">
update employee set name = #{name}, address = #{address} where id = #{id}
</update>
<delete id="daleteEmp">
delete from employee where id = #{id}
</delete>
</mapper>
IEmployeeDao.java
public interface EmployeeDao {
//查询所有
public List<Employee> selAll();
//查询单一
public Employee selOne(String id);
//新增
public void insertEmp(Employee emp);
//修改
public void updateEmp(Employee emp);
//删除
public void daleteEmp(String id);
}
测试代码:
public class Demo {
public static void main(String[] args) throws IOException{
Reader reader = Resources.getResourceAsReader("resource.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sessionFactory.openSession();
IEmployeeDao mapper = session.getMapper(IEmployeeDao.class);
mapper.insertEmp(new Employee(5, "Lucy", '1', "Lucy@163.com"));
mapper.daleteEmp("4");
mapper.updateEmp(new Employee(5, "Lucy", '1', "Lucy@qq.com"));
System.out.println(mapper.selOne("2"));
session.commit();
session.close();
}
}
MyBatis的传参
1、出错演示
好了,增删查改的基本功能已经演示完毕,但是假设我们需要做一个多条件查询的方法,则需要这样写(一下代码均为省略代码,只贴上了重要的代码,其他的代码均在CURD的演示中): Employee.xml
<select id="queryList" resultType="com.xpu.bean.Employee">
select * from employee where name like '#{name}%' and gender = #{gender}
</select>
IEmployeeDao.java
//多条件查询
public List<Employee> queryList(String name, char gender);
多条件查询测试:
public void test() {
IEmployeeDao mapper = session.getMapper(IEmployeeDao.class);
List<Employee> list = mapper.queryList("t", '1');
for (Employee employee : list) {
System.out.println(employee);
}
}
但是却报错了,注意红线标记的地方,我们需要修改参数的传递方式:
2、顺序传参法
其实对于这种多参数的情况,应该这样写Employee.xml,这叫做顺序传参法:
<select id="queryList" resultType="com.xpu.bean.Employee">
select * from employee where name like '${param1}%' and gender = #{param2}
select * from employee where name = #{0} and gender = #{1}
</select>
#{ }
里面的数字代表你传入参数的顺序,SQL语句的语义表达不直观,且一旦顺序调整容易出错,所以不建议使用!
3、注解传参法
但是这样写的可读性太差,可以使用注解的方式:
<select id="queryList" resultType="com.xpu.bean.Employee">
select * from employee where name like '${name}%' and gender = #{gender}
</select>
//多条件查询
public List<Employee> queryList(@Param("name") String name, @Param("gender") char gender);
这样可读性强,而且不会因为参数的顺序问题导致程序异常!
4、Map传参法
Employee.xml
<select id="queryList" resultType="com.xpu.bean.Employee">
select * from employee where name like '${name}%' and gender = #{gender}
<select>
IEmployeeDao.java
public List<Employee> queryList(Map map);
测试代码:
public void test() {
IEmployeeDao mapper = session.getMapper(IEmployeeDao.class);
Map map = new HashMap();
map.put("name", "t");
map.put("gender", '1');
List<Employee> list = mapper.queryList(map);
}
5、JavaBean传参法
这个在MyBatis的CURD中已经演示过了,resultType="com.xpu.bean.Employee"
,通过#{ 对象属性 }
便可以传参,在此不再赘述!
MyBatis动态SQL
MyBatis 的强大特性之一便是它的动态 SQL。你可以在这个网站学习动态SQL 《动态 SQL》 ,如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
if
就是简单的条件判断,利用if语句我们可以实现某些简单的条件选择
<!-- 动态SQL -->
<select id="queryByIf" resultType="com.xpu.bean.Employee">
select * from employee where 1=1
<if test="name != null">
and name like '${name}%'
</if>
</select>
但是在上面的例子中,如果name为空的话,where后面就什么都没有,所以加上1=1
完全是为了保证语法的正确性,这个问题将在下面解决!
where+if
where标签知道如果它包含的标签中有返回值的话,它就插入一个where。此外,如果标签返回的内容是以AND 或 OR开头的,则它会剔除掉。
<select id="queryByWhere" resultType="com.xpu.bean.Employee">
select * from employee
<where>
<if test="name != null">
name like '${name}%'
</if>
<if test="address != null">
and address = #{address}
</if>
</where>
</select>
上述例子中,如果name不为空,address不为空,则SQL语句是select * from employee where name like '${name}%' and address = #{address}
,即使name为空,address前面的and也会被去掉,同样是合法的SQL语句,如果都为空,那就变成了查询所有记录,同样也是合法的SQL语句!
trim
trim标签是可以让我们自己去实现一个where标签,或者实现更多的功能,下面用trim标签去实现一个where标签:
<select id="queryByTrim" resultType="com.xpu.bean.Employee">
select * from employee
<trim prefix="where" prefixOverrides="AND | OR">
<if test="name != null">
name like '${name}%'
</if>
<if test="address != null">
and address = #{address}
</if>
</trim>
</select>
这个trim标签其实就是一个可以通过自定义属性去完成一些功能的标签,prefix="where"
就是代表如果标签中有内容则加上where,如果where后面直接遇到AND或者OR这样的标签就会将其剔除!
set
<update id="updateEmployee" parameterType="com.xpu.bean.Employee">
update user
<set>
<if test="name!=null">
name=#{name}
</if>
</set>
<where>
<if test="address!=null">
address=#{address}
</if>
</where>
</update>
set标签代替了sql中set关键字,set标签可以自动去除sql中的多余的','
,这个和where标签是一样的道理,同样的我们可以使用trim来实现同样的功能:
<update id="updateEmployee" parameterType="com.xpu.bean.Employee">
update user
<trim prefix="set" prefixOverrides=",">
<if test="name!=null">
name=#{name}
</if>
</trim>
<where>
<if test="address!=null">
address=#{address}
</if>
</where>
</update>
choose
chose标签类似于流程控制中switch case default语句,配合when、otherwise标签使用:
<select id="queryByChoose" resultType="com.xpu.bean.Employee">
select * from employee where 1=1
<choose>
<when test="name != null">
and name like '${name}%'
</when>
<when test="address != null">
and address = ${address}
</when>
<otherwise>
order by name
</otherwise>
</choose>
</select>
foreach
foreach标签可迭代任何对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数,下面是一个根据ID批量删除的示例:
<delete id="deleteByList">
delete from employee where id in
<foreach collection="list" open="(" separator="," close=")"
item="myitem">
#{myitem}
</foreach>
</delete>
<delete id="deleteByArray">
delete from employee where id in
<foreach collection="array" open="(" separator="," close=")"
item="myitem">
#{myitem}
</foreach>
</delete>
//批量删除
public Integer deleteByList(List<String> list);
//批量删除
public Integer deleteByArray(String[] strs);
public void func() throws IOException {
Integer array = mapper.deleteByArray(new String[] {"1", "2"});
System.out.println("影响行数:" + array);
List<String> list = new ArrayList<>();
list.add("3");
list.add("4");
Integer deleteByList = mapper.deleteByList(list);
System.out.println("影响行数:" + deleteByList);
session.commit();
session.close();
}
动态 SQL 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 SQL 语句出来,然后在通过 MyBatis 动态SQL 对照着改,防止出错!