Mybatis框架概述
前端框架
-
前端开发的框架
Angular.js,React.js,Vue.js -
前端UI框架
方便后端人员实现精美的前端页面搭建——Extjs,jquery ui,easy ui,bootstrap,layui -
后端框架
表现层框架(Controller):servlet
struts,xwork,struts2,springmvc
在SpringMVC编程中一般分为四层:
1.表示层:(jsp、html 主要就是界面的展示)
2.控制层:(Contoller、Action)控制界面跳转
3.业务层:(Service)调用DAO层,实现解耦合目的,虽然不要它也可以运行项目,但是会使项目后期的延展和维护变得困难
4.持久层:(DAO)也叫数据访问层,实现对数据库的访问
持久层框架(Dao)JDBC:Hibernate(hql),ibtais(xml),Mybatis
整合框架:EJB,spring
SSH:struts/struts2 spring hibernate
SSM:springmvc spring mybatis
Mybatis是一个优秀的持久层框架,它对jdbc几乎所有的数据库操作进行了封装(包括加载驱动、创建connection、创建statement、手动设置参数、结果集检索等繁琐操作),使开发者只需要关注SQL本身。
Mybatis框架搭建
准备工作
创建学生表,创建基础数据
创建学生表:
对应数据库表构造Student类:
package com.sinosoft.domain;
public class Student {
private String id;
private String name;
private Integer age; //Integer可以表示空值
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
框架搭建
准备工作完成后开始进行框架搭建:
- 创建项目,搭建包结构
- 导入mybatis相关jar包和MySQL驱动包
- 导入log4j相关jar包
- 在src根下创建mybatis主配置文件mybatis-config.xml,搭建配置文件结构——dbcp,c3p0,druid alibaba 文档第三页 如下1-1
- 创建mapper包结构,创建SQL映射文件XxxMapper.xml 文档第四页 如下1-2
- 在src根下引入log4j属性文件
- 搭建测试程序,测试根据ID查单条——关键语句 模板在文档第二页 如下1-3
1-1:
<?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/test"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- <mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
</mappers>
</configuration>
1-2:
<?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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
1-3:
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
数据库运行测试
框架搭建好后,进行数据库运行测试:
package com.sinosoft.test;
import com.sinosoft.domain.Student;
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 java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class Test1 {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
//输入流
InputStream inputStream = null;
try {
//通过加载Mybatis的主配置文件mybatis-config.xml,创建输入流对象
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
/*
SqlSessionFactoryBuilder:SqlSessionFactory的建造者,
通过该建造者对象调用建造方法,为我们创建一个SqlSessionFactory对象
sqlSessionFactory对象唯一的作用就是为我们创建SqlSession对象
*/
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//我们未来所有的操作,使用的都是SqlSeeion对象
//例如增删改查,处理事务等等,都是统一使用session对象来完成
SqlSession session = sqlSessionFactory.openSession();
/*
需求:根据id查单条
如果取得的是单挑记录,我们调用的是selectOne方法
参数1:根据命名空间,sqId的形式找到我们需要使用的sql语句
参数2:我们要为sql语句中传递的参数
*/
Student s = session.selectOne("test1.getById","A0002");
System.out.println(s);
session.close();
//查询学生信息表中所有的记录
List<Student> sList = session.selectList("test1.getAll");
for(Student s:sList){
System.out.println(s);
}
session.close();
//数据库添加操作
Student s = new Student();
s.setId("A0005");
s.setName("cxk");
s.setAge(66);
int count = session.insert("test1.add",s);
System.out.println("================="+count);
session.commit(); //mybatis是手动提交事务
session.close();
//数据库修改操作
Student s = new Student();
s.setId("A0001");
s.setName("sjkskks");
s.setAge(111);
session.update("test1.update",s);
session.commit();
session.close();
//数据库删除操作
session.delete("test1.delete","A0004");
session.commit();
session.close();
}
}
对应的mapper.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="test1">
<!--
namespace:命名空间
不同的mapper映射文件使用namespace来做区分
不同的mapper映射文件所使用的namespace的命名不允许出现重复
sql语句必须要写在相应的标签当中
<insert>:在标签对中写insert开头的sql语句,处理添加操作
<update>:在标签对中写update开头的sql语句,处理修改操作
<delete>:在标签对中写delete开头的sql语句,处理删除操作
<select>:在标签对中写select开头的sql语句,处理查询操作
parameterType:为sql语句传递的参数
resultType:为sql语句操作后返回的结果类型
注意在未来实际项目开发中,所有的标签必须要写id属性
<selsct>标签parameterType可以省略不写
resultType属性必须得写
对于<insert><update><delete>这3个标签
通常我们只写id属性,其他属性一概不写
-->
<select id="getById" parameterType="java.lang.String" resultType="com.sinosoft.domain.Student">
select * from student where id=#{id}
</select>
<!--
如果返回的是多条记录,那么resultType返回值类型,应该写为集合的泛型
-->
<select id="getAll" resultType="com.sinosoft.domain.Student">
select * from student
</select>
<insert id="add" parameterType="com.sinosoft.domain.Student">
insert into student(id,name,age) values (#{id},#{name},#{age})
</insert>
<update id="update" parameterType="com.sinosoft.domain.Student">
update student set name=#{name},age=#{age} where id=#{id}
</update>
<delete id="delete">
delete from student where id=#{id}
</delete>
</mapper>
MyBatis结合dao层的开发
结合原始dao的开发
MyBatis对dao层动态代理的支持
我们以前用过动态代理,我们是在业务层使用的,在业务层使用动态代理是为了实现事务管理,业务层的动态代理是我们自己手写的,业务层之所以使用动态代理,是因为业务层本身就是用来处理业务逻辑的,事务相关的代码,不方便放在业务层处理,所以我们想到使用代理类帮业务层去处理。
现在我们要在dao层也要加入动态代理。dao层之所以创建代理类,是因为写dao层实现类本身就是一种不方便,在结合了MyBatis动态代理机制后,以后的实际项目开发,dao层的impl就不写了。Mybatis的动态代理不用我们自己手写,在Mybatis中已经集成好的一种机制,我们直接拿来使用就可以。
开发规则:
- 在Mapper.xml中将namespace设置为UserDao.java接口的权限定名
<mapper namespace="sinosoft.dao.StudentDao">
- 将Mapper.xml中statement的id和UserDao.java接口的方法名保持一致
<select id="getById" parameterType="java.lang.String" resultType="com.sinosoft.domain.Student">
select * from student where id=#{id}
</select>
<insert id="save">
insert into student(id,name,age) values(#{id},#{name},#{age})
</insert>
-
将Mapper.xml中statement的parameterType和UserDao.java接口的方法输入参数类型保持一致
-
将Mapper.xml中statement的resultType和UserDao.java接口的方法输出结果类型保持一致
-
Dao业务层动态代理提交事务。
将事务处理直接写进ServiceIMPL中,而获取成员属性的时候直接从Dao层中的UserDao的接口中获取并转换成session对象。之后将Dao层中的DaoIMPL删除,将mapper.xml配置文件放入Dao层中,更改mapper.xml中的namespace路径映射到UserDao.class中,最后再将mybatis-config.xml中的mapper源路径更新一下
MyBatis全局配置文件
-
properties:
加载数据库连接属性文件
-
settings全局配置参数
Mybatis框架运行设置一些全局配置参数,比如:开启二级缓存,开启延迟载等等
详细参考:mybatis-settings.docx
注意:设置全局参数会影响mybatis框架运行,要谨慎设置
<!--
设置与数据库交互的环境,可以在此处配置二级缓存,配置查询延迟加载策略等等
配置的目的是为了更加有效的查询表中的记录
在实际项目开发中,settings的设置基本没用
因为settings对于查询的优化,得到的效果不明显
对于海量级别的数据,使用settings配置优化,起不到任何的效果
对于数据量较少的项目,对于查询的效率要求的比较低,也没有必要使用settings配置
如果遇到了海量级别的数据,我们该如何去提高查询的效率呢?
基础操作:
对于常用的查询条件的字段,设置索引(相当于在书里加目录)
高级操作:
使用nosql数据库,redis(缓存数据库)
专业操作:
搜索引擎solar和Elasticsearch
针对于电商行业
-->
<settings>
<setting name="" value=""/>
</settings>
-
typeAliases
设置类型别名,框架默认支持的别名,参见Mybatis默认支持的别名.docx
表示为mapper映射文件中的domain起别名
<typeAliases>
<!--
方式1:
为指定的类分别起别名,别名的命名由我们自己来决定
type:要为哪个domain起别名, 填写包,类名称
alias:别名的名字
-->
<!-- <typeAlias type="com.sinosoft.domain.Student" alias="stu"/>-->
<!--
方式2:
使用package标签批量起别名
别名是Mybatis默认为我们取好的,命名不是由我们自己决定,别名为类名(类名的字母不区分大小写)
虽然字母不区分大小写,但是还是要按照约定俗成的规则来进行填写
name:指定一个包结构,表示在该包下,所有的domain自动起好了别名
-->
<!--
总结:
(1)如果未来实际项目开发中,如果公司需要使用起别名的机制,我们要用批量起别名的方式
(2)在市场上也有很多企业崛起使用Mybatis起别名的机制
公司会认为将domain写成全路径,可以有效的提供代码的可读性
-->
<package name="com.sinosoft.domain"/>
</typeAliases>
指定mapper映射文件路径:
<mappers>
<!--
方式1:使用resource属性,指定mapper映射文件
<mapper resource="com/sinosoft/dao/StudentDao.xml"/>
-->
<!--
方式2:
使用class属性,找到dao层接口的全路径
<mapper class="com.sinosoft.dao.StudentDao"/>
-->
<!--
方式3:
批量注册,name属性,指向dao层的包,表示在该dao包下,所有的mapper映射文件自动注册
-->
<!--
总结:未来实际项目开发中,我们一定是批量注册mapper映射文件
-->
<package name="com.sinosoft.dao"/>
</mappers>
Mybatis映射文件
-
设置参数类型:
parameterType用于设置输入参数的Java类型,parameterType的值为参数类型的Java类型或者别名,Sql语句获取参数的值使用#{}或者${},使用时可省略
(1)使用简单数据类型(8基本数据类型+String)为参数
(2)使用引用数据类型为参数
(3)使用domain对象类为参数
(4)使用map为参数
package com.sinosoft.test;
import com.sinosoft.dao.StudentDao;
import com.sinosoft.util.SqlSessionUtil;
public class Test2 {
public static void main(String[] args) {
StudentDao studentDao = SqlSessionUtil.getSession().getMapper(StudentDao.class);
// //1.测试:parameterType,使用简单数据类型 String
// Student s = studentDao.select1("A0001");
// System.out.println(s);
//2.测试,parameterType使用简单数据类型int
//查询出所有年龄为23岁的学员的详细信息
// List<Student> sList = studentDao.select2(11); //返回List集合,因为年龄11的不仅仅只有一个
// for (Student s:sList){
// System.out.println(s);
// }
//3.测试parameterType
//需求:查询出姓名为wsc,年龄为18岁的学员信息
/*
绝对不可以同时为sql语句传递多个参数
*/
// List<Student> studentList = studentDao.select3("wsc",18);
// for (Student s:studentList){
// System.out.println(s);
// }
//如果我们要为sql语句传递多个参数,我们应该将多个参数封装到一个domain对象中,或者是打包到一个map集合中
//4.测试parameterType,使用domain为参数Student s
//需求:查询出姓名为wsc,年龄为18岁的学员信息
// Student s = new Student();
// s.setName("wsc");
// s.setAge(18);
// List<Student> sList = studentDao.select4(s);
// for (Student ss:sList){
// System.out.println(ss);
// }
//5.测试parameterType,使用map为参数
//需求:查询出姓名为wsc,年龄为18岁的学员信息
// Map<String,Object> map = new HashMap<String,Object>();
// map.put("name","wsc");
// map.put("age",18);
// List<Student> sList = studentDao.select5(map);
// for (Student ss:sList){
// System.out.println(ss);
// }
/*总结:
在实际项目开发中,使用domain引用类型,或者是使用map集合类型都可以为sql语句同时传递多个参数
一般情况下,我们使用的domain就可以了
当domain不符合需求的情况下,我们一定要考虑使用map来传值
例子:需求:请查询出姓名为wsc,班级为一年一班的学员的详细信息
select
from student
join classroom c
on s.classroomId=c.id
where s.name=#{wsc} and c.name={一年一班}
在实际项目开发中,一定要学会使用为sql传值的这几种方式
但是对于在<select>中的parameterType属性,一般我们都是省略不写的
*/
}
}
对应的mapper.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.sinosoft.dao.StudentDao">
<!--<select id="getAll" resultType="Student">
select * from student;
</select>
<select id="getById" parameterType="java.lang.String" resultType="com.sinosoft.domain.Student">
select * from student where id=#{id}
</select>
<insert id="save">
insert into student(id,name,age) values(#{id},#{name},#{age})
</insert>-->
<!--
对于parameterType:
java.lang.String好使
String好使
string好使
strinG好使
STRING好使
str不好使
省略不写好使
-->
<!--
使用简单类型(8大基本数据类型+String)为参数
在#{}中的标识符可以随意去写
但是虽然可以随意写,还是要写的有意义
-->
<select id="select1" parameterType="string" resultType="Student">
select * from student where id=#{id};
</select>
<select id="select2" parameterType="int" resultType="Student">
select * from student where age=#{age};
</select>
<select id="select3" resultType="Student">
select * from student where name=#{name} and age=#{age};
</select>
<!--
如果我们为sql语句传递的参数类型为一个domain引用类型,那么#{}中的标识符必须是domain类的属性名
-->
<select id="select4" parameterType="Student" resultType="Student">
select * from student where name=#{name} and age=#{age};
</select>
<!--
如果我们为sql语句传递的参数类型为一个map类型
那么#{}中的标识符必须是map的key
-->
<select id="select5" parameterType="map" resultType="Student">
select * from student where name=#{name} and age=#{age};
</select>
</mapper>
-
#{}与${}
使用在sql语句中的符号
(1)#{}:表示占位符,可以有效防止sql注入,使用#{}设置参数无需考虑参数的类型(2) : 表 示 拼 接 符 , 无 法 防 止 s q l 注 入 , 使 用 {}:表示拼接符,无法防止sql注入,使用 :表示拼接符,无法防止sql注入,使用{}设置参数必须考虑参数的类型
对应的mapper.xml映射文件
(3)传递简单类型参数:如果获取简单类型参数,#{}中可以使用value或其他名称。如果获取简单类型参数, 中 只 能 使 用 v a l u e 。 注 意 引 号 的 引 入 s e l e c t ∗ f r o m s t u d e n t w h e r e i d = ‘ {}中只能使用value。注意引号的引入 select * from student where id=‘ 中只能使用value。注意引号的引入select∗fromstudentwhereid=‘{value}’
(4) 在没有特殊要求的情况下,通常使用#{}占位符
(5)有些情况必须使用 , 比 如 : 需 要 动 态 拼 接 表 名 , s e l e c t ∗ f r o m {},比如:需要动态拼接表名,select*from ,比如:需要动态拼接表名,select∗from{tablename},比如:动态拼接排序字段:select*from tablename order by ${username} desc
(6)重点案例:
使用${}执行like模糊查询
使用#{}执行like模糊查询
//7.测试:Like模糊查询 方式1:使用${}了解即可
/*List<Student> sList = studentDao.select7("s");
for (Student ss:sList){
System.out.println(ss);
}*/
//8.测试:Like模糊查询 方式1:使用#{} 了解
/*List<Student> sList = studentDao.select8("%s%");
for(Student ss:sList){
System.out.println(ss);
}*/
//9.测试:Like模糊查询 方式1:使用#{} 掌握
/*List<Student> sList = studentDao.select9("s");
for (Student ss:sList){
System.out.println(ss);
}*/
对应的mapper.xml映射
<select id="select7" resultType="Student">
select * from student where name like '%${value}%';
</select>
<select id="select8" resultType="Student">
select * from student where name like #{"%s%"};
</select>
<!--
'%'空格#{}空格'%'
以上空格不能省略
-->
<select id="select9" resultType="Student">
select * from student where name like '%' #{name} '%';
</select>
- resultType
设置返回值类型:
(1)返回简单类型
//10.测试:resultType返回String类型
//需求:查询出编号为A0001的学员的姓名
/*String name = studentDao.select10("A0001");
System.out.println(name);*/
//11.测试:resultType返回String类型集合
//需求:查询出所有学生的姓名
/*List<String> sList = studentDao.select11();
for (String name:sList){
System.out.println(name);
}*/
//12.测试:resultType返回int类型
//需求:查询出表中一共有多少条信息
/*int count = studentDao.select12();
System.out.println(count);*/
对应的mapper.xml映射
<select id="select10" resultType="String">
select name from student where id=#{id};
</select>
<select id="select11" resultType="String">
select name from student;
</select>
<select id="select12" resultType="int">
select count(*) from student;
</select>
(2)返回pojo(domain Student)
//13.测试:resultType 返回domain引用类型Student
// Student student = studentDao.select6("A0001");
// System.out.println(student);
对应的mapper.xml映射文件
<select id="select6" resultType="Student">
select * from student where id='${value}';
</select>
(3)返回hashmap(注意返回值类型)
//14.测试:resultType 返回map类型
/*
* <select id="" resultType="Student">
select * from student;
</select>
* 当执行了sql语句后,通过查询得到的结果id,name,age
* 根据返回值的类型,会自动为我们创建出来一个该类型的对象,由该对象将查询的结果封装起来
*
* Student s1 = new Student();
* s.setId("A0001");
* s.setName("sjkskks");
* s.setAge(23);
*
* 当查询出来了第二条记录,根据返回值类型,再一次创建出来一个对象,封装第二条记录的值
* Student s2 = new Student();
* s.setId("A0002");
* s.setName("lh");
* s.setAge(11);
*
* Student s6 = new Student();
* s.setId("A0006");
* s.setName("SA");
* s.setAge(2);
*
* 多条记录封装成为了多个Student对象
* Mybatis系统会自动创建出来一个List集合来保存这些对象
* List<Student> sList = new ArrayList();
* sList.add(s1);
* sList.add(s2);
* ...
* sList.add(s6);
*
*
* --------------------------------------------------
*
* <select id="" resultType="Student">
select * from student;
</select>
*
* 当执行了sql语句之后,通过查询得到的结果id,name,age
* 根据返回值类型,map会自动为我们创建出来一个该类型的对象,由该对象将查询的结果封装起来
* Map<String,Object> map1 = new HashMap<>();
* map1.put("id","A0001");
* map1.put("name","sjkskks");
* map1.put("age","23");
*
* 当查询出来了第二条记录,根据返回值类型,再一次创建出来一个对象,封装第二条记录的值
*
* 当执行了sql语句之后,通过查询得到的结果id,name,age
* 根据返回值类型,map会自动为我们创建出来一个该类型的对象,由该对象将查询的结果封装起来
* Map<String,Object> map2 = new HashMap<>();
* map1.put("id","A0002");
* map1.put("name","lh");
* map1.put("age","11");
* ...
* ...
* map6.put()...;
*
*
*多条记录封装成为了多个map对象
*Mybatis系统会自动创建出来一个map集合来保存这些对象
* List<Map<String,Object>> mapList = new ArrayList<>();
* mapList.add(map1);
* mapList.add(map2);
* ...
* mapList.add(map6);
*
* 对于sql语句查询的结果,我们使用domain来封装这些结果,很方便,为何还使用map呢??
* 因为对于查询的结果有很多的情况,使用domain来封装不了,所以我们使用map来保存结果
*
* 例如:
* 需求:根据姓名来分组,查询出来每一个姓名对应的数量
* 叫sjkskks的多少人,叫lh的多少人
* ...
*
* select name,count(*)
* from student
* group by name
*
* 对于以上查询结果,使用domain能封装结果值吗?
* 不能,因为domain有name属性,但是没有count属性
*
* 所以返回map一定可以保存查询得到的结果
*
* */
/*List<Map<String, Object>> mapList = studentDao.select14();
for (Map<String, Object> map : mapList) {
Set<String> set = map.keySet(); //将键值拆开
for (String key : set) {
System.out.println("key:" + key);
System.out.println("value:" + map.get(key)); //通过拆开的键值来遍历查找map中的value值
}
System.out.println("---------------------------------------");
}*/
对应的mapper.xml映射
<select id="select14" resultType="map">
select * from student;
</select>
(4)当查询字段名和pojo属性名不一致时的解决方案
a.为字段起别名,别名为类中属性名
//15.测试,resultType 当数据库表字段名称和domain类属性名称不一致时的处理,方式一:起别名
/*List<Student> studentList = studentDao.select15();
for (Student name:studentList){
System.out.println(name);
}*/
对应的mapper.xml映射
<!--
resultMap 是用来做数据库表字段和类属性名一一对应关系所用的标签
id:resultMap标签对的唯一标识,将来在使用该resultMap标签的时候,使用id来找到这组标签
type:指定一个类型,要与数据库表一一对应,建立起表字段和类属性的名字一一匹配的关系
-->
<select id="select15" resultType="Student">
select
id,
fullname as name,
age
from student;
</select>
b.使用resultMap在mapper.xml映射文件中将表字段与domain类属性名称建立起一一匹配的关系
//16.测试,resultType 当数据库表字段名称和domain类属性名称不一致时的处理,方式二:使用resultMap
/*List<Student> studentList = studentDao.select16();
for (Student name:studentList){
System.out.println(name);
}*/
对应的mapper.xml映射
<!--
resultMap 是用来做数据库表字段和类属性名一一对应关系所用的标签
id:resultMap标签对的唯一标识,将来在使用该resultMap标签的时候,使用id来找到这组标签
type:指定一个类型,要与数据库表一一对应,建立起表字段和类属性的名字一一匹配的关系
-->
<resultMap id="stuMap" type="Student">
<!--
id标签:用来配置主键的对应关系
result标签:用来配置普通字段对应关系的
对于student表,表结构是一个id,两个普通字段
所以需要一个id标签,两个result标签
property属性:配置的是类中的属性名
column属性:配置的是表中的字段名
这样就能够建立起类属性的表字段一一对应的关系
-->
<id property="id" column="id"/>
<result property="name" column="fullname"/>
<result property="age" column="age"/>
</resultMap>
<select id="select16" resultMap="stuMap">
select * from student;
</select>
Mybatis动态SQL机制
使用动态sql,动态拼接sql语句
情况1:如果查询数据什么都不填
String sql = “select * from tbl”;
情况2:只填写了姓名
String sql = “select * from tbl where name like ?”;
情况3:只填写了姓名与性别
String sql = “select * from tbl where name like ? and gender=?”;
情况4:只填写了姓名与性别,年龄
String sql = “select * from tbl where name like ? and gender=? and age=?”;
等等等等。。。。多种查询匹配组合
在实际项目开发中,我们对于这种需求,肯定是要将sql语句写成动态的形式,动态sql语句的核心思想是,有哪个查询条件,就动态的在where关键字后面挂载哪个查询条件
String sql = “select * from tbl”;
if(name != null) {
sql += “where name like ?”;
}
if(gender != null) {
sql += “and gender=?”;
}
where if 标签的使用:
实现代码如下:
//17.测试,动态sql where标签+if标签
Student student = new Student();
//student.setName("s");
student.setAddress("撒");
List<Student> studentList = studentDao.select17(student);
for (Student s:studentList){
System.out.println(s);
}
}
对应的mapper.xml映射:
<select id="select17" parameterType="Student" resultType="Student">
select * from student
<!--where标签:
当where标签在使用的时候,必须要搭配where标签对中的if标签来使用
当通过if标签的判断,如果有查询条件,则展现where关键字,如果没有查询条件则不展现where关键字
-->
<where>
<if test="name!=null and name!= ''">
name like '%' #{name} '%'
</if>
<if test="address!= null and address!=''">
and address like '%' #{address} '%'
</if>
</where>
</select>
foreach标签的使用:
实现代码如下:
//18.测试:动态sql foreach标签
//查询编号为A0001,A0002,A0003的学员信息
String strArr[] = {"A0001", "A0002", "A0003"}; //前端往后端传用数组,后端往前端传用集合
List<Student> studentList = studentDao.select18(strArr);
for (Student student : studentList) {
System.out.println(student);
}
对应的mapper.xml映射文件:
<select id="select18" resultType="Student">
select * from student
where id in
<!--
foreach标签:用来遍历传递来的数组参数
collection:标识传递参数的类型
array:数组
List:集合
item:每一次遍历出来的元素,在使用该元素的时候,需要用在#{}中
open:拼接循环开始符号
close:拼接循环结束的符号
separator:元素与元素之间的分割符
-->
<foreach collection="array" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
mybatis中的sql片段的使用:
实现代码如下:
//19.测试:sql片段
Student s = studentDao.select19("A0001");
System.out.println(s);
对应的mapper.xml映射:
<!--
使用sql标签制作sql片段
sql片段的作用是用来代替sql语句中的代码
如果你的mapper映射文件中的sql语句某些代码出现了大量的重复,我们可以使用sql片段来代替他们
id:sql片段的唯一标识,将来找到sql片段使用id来进行定位
将来的实际项目开发中,使用sql片段用来代替重复率高,且复杂的子查询
select * from student
where name=(
select xxxxx
xxxxx
(
select .....
)
)
注意:
对于sql片段
在同一个mapper下,大量的出现重复的子查询的几率不高,所以一般情况下没有使用sql片段的必要
在实际项目开发中,如果你大量的使用sql片段,会大大的降低sql语句的可读性
在很多企业中,干脆摒弃使用sql片段的机制
-->
<sql id="sql1">
select * from student
</sql>
<select id="select19" resultType="Student">
<include refid="sql1"/> where id=#{id}
</select>
Mybatis多表联查
关联学生表和班级表:
- 查询出学生姓名和班级名称
新建班级表:
实现代码如下:
//20.测试:多表联查 查询出学生姓名和班级名称
//多表联查返回的数据有时候不能用domain来返回,所以最好用map
List<Map<String,Object>> mapList = studentDao.select20();
for (Map<String,Object> map:mapList){
Set<String> set = map.keySet(); //key不可重名
for (String key:set){
System.out.println("key:"+key);
System.out.println("value:"+map.get(key));
}
System.out.println("--------------------------------------------------");
}
对应mapper.xml映射文件如下:
<select id="select20" resultType="map">
select
s.name as sname,
c.name as cname
from student s
join classroom c
on s.classroomId=c.id
</select>
- 查询出学生和班级所有信息,加VO(Value Object)
实现代码如下:
//21.测试:多表联查 查询出学生和班级所有信息,加VO
/*
* 在实际项目开发中,如果需要为前端展现的数据,使用一个domain类型不足以表现出来这些数据
* 这时,我们可以考虑使用两种技术来实现
* 分别为:
* 使用map以及使用vo
*
* 例如我们现在的需求
* 查询出学生和班级所有信息
* 得到的结果 使用学生的domain或者班级的domain都不能够封装这些结果
* 所以我们可以使用map去保存这些信息
* 同时我们也可以使用vo类来保存这些信息
*
* vo指的是创建出来一个类,这个类中的属性是完全由我们自己去定义,属性会保存所有需要展现的信息,
* 例如我们现在的这个例子,我们可以使用vo来封装所有与学生和班级相关的信息
*
* vo
* student
* classroom
* */
List<StudentAndClassroomVo> voList = studentDao.select21();
for (StudentAndClassroomVo vo:voList){
System.out.println(vo);
}
对应的mapper.xml映射文件为:
<select id="select21" resultType="com.sinosoft.vo.StudentAndClassroomVo">
select
s.id sid,
s.name sname,
s.age sage,
s.address saddress,
c.id cid,
c.name cname
from student s
join classroom c
on s.classroomId=c.id
</select>
- 查询出带有字母Z的学生和班级所有信息
实现代码如下:
//22.测试:多表联查 查询出带有字母z的学生和班级所有信息
List<StudentAndClassroomVo> voList = studentDao.select22("s");
for (StudentAndClassroomVo vo:voList){
System.out.println(vo);
}
/*
* 实际项目开发中,如果要为前端同时提供多组值,那么我们应该使用map还是vo呢?
* 如果前端的需求的重复率不高,那么我们选择临时使用map就可以了
* 如果前端对于该需求的重复率较高,那么我们可以创建一个vo类来使用,非常方便
* */
对应的mapper.xml映射文件:
<select id="select22" resultType="com.sinosoft.vo.StudentAndClassroomVo">
select
s.id sid,
s.name sname,
s.age sage,
s.address saddress,
c.id cid,
c.name cname
from student s
join classroom c
on s.classroomId=c.id
where s.name like '%' #{name} '%'
</select>
Mybatis工具包(ServiceFactory等)
SqlSessionUtil的实现代码如下:
package com.sinosoft.util;
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 java.io.IOException;
import java.io.InputStream;
public class SqlSessionUtil {
private SqlSessionUtil(){
}
private static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
private static ThreadLocal<SqlSession> t = new ThreadLocal<SqlSession>(); //因为在声明的时候已经确定好了泛型,创建对象的时候jvm会自动进行类型转换
//取得sqlsession对象
public static SqlSession getSession(){
SqlSession session = t.get();
if (session == null){
session = sqlSessionFactory.openSession();
t.set(session);
}
return session;
}
//关闭sqlsession对象
public static void myClose(SqlSession session){
if(session != null){
session.close();
t.remove(); //这句必须加,非常容易忘,不然会在ThreadLocal存在遗留
}
}
}
TransactionInvocationHandler的代码实现如下:
package com.sinosoft.util;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class TransactionInvocationHandler implements InvocationHandler {
//target:zs
private Object target;
public TransactionInvocationHandler(Object target){
this.target = target;
}
//代理类的业务方法
//代表李四的表白方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession session = null;
Object obj = null;
try{
session = SqlSessionUtil.getSession();
//处理业务逻辑
//method.invoke,代表张三的表白方法
obj = method.invoke(target,args);
//处理业务逻辑完毕后,提交事务
session.commit();
}catch(Exception e){
session.rollback();
e.printStackTrace();
}finally {
SqlSessionUtil.myClose(session);
}
return obj;
}
//取得李四对象
public Object getProxy(){
//取得代理类对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
}
ServiceFactory的代码实现如下:
package com.sinosoft.util;
public class ServiceFactory {
//传递张三对象,得到李四对象的过程
public static Object getService(Object service){
return new TransactionInvocationHandler(service).getProxy();
}
}