小感悟:
自学Java也有一年了,基本上都是学习基础的教程,Java水很深,前面学习新知识后面忘了老知识,我也不知道关于Java中的东西反反复复的敲了多少次。大学同学在某个培训机构里面培训了仅仅三个月就去工作了,笔者和培训后的同学交流后感觉技术不过如此。Java的学习道路很是艰难,虽然我毕业后没能去从事软件开发,但是我想用时间来沉淀一下自己的技术,今天我们就来谈谈Mybatis技术。
使用技术:Maven+Mybatis
开发工具:IDEA
1 HelloWorld
创建Maven项目并且在pom.xml文件中导入Mybatis依赖
org.mybatis
mybatis
RELEASE
数据库中创建一个book表
CREATE TABLE `book` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`author` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建实体类并且生成set和get以及toString方法
mybatis参考文档中复制约束文件(Mybatis官方参考网址)
http://www.mybatis.org/mybatis-3/zh/getting-started.html
参考官方文档创建mybatis核心配置文件
pom.xml文件中引入Mysql驱动依赖
mysql
mysql-connector-java
RELEASE
创建数据库配置文件db.properties
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql:///test_mybatis
db.username=root
db.password=xxxxxxxxxxx
在mybatis核心配置文件中引入数据库配置文件
将${}里面的内容更改为${db.properties}
创建mapper包并且在该包下面创建接口和xml文件
BookMapper接口中添加内容为
public interface BookMapper {
int addBook(Book book);
}
BookMapper.xml文件里面的内容为
insert into book set name="浅谈mybatis",author="孤芳不自赏";
核心配置文件中引入mapper.xml文件
创建测试类
public class test_mybatis {
public static void main(String[] args)throws IOException {
//读取mybatis核心配置文件,抛出异常
InputStream r = Resources.getResourceAsStream("mybatis-conf.xml");
//创建sqlSession工厂
SqlSessionFactory sqf =new SqlSessionFactoryBuilder().build(r);
//通过工厂来获取session对象
SqlSession session = sqf.openSession();
//通过session来进行响应的crud操作
/**
* 添加操作
*/
int i = session.insert("addBook");
session.commit();
if (i>0){
System.out.println("添加成功!");
}else{
System.out.println("添加失败!");
}
session.close();
}
}
此时运行会报如下错误(未发现核心配置文件)
Cause: java.io.IOException: Could not find resource com/mapper/BookMapper.xml
pom.xml文件中添加内容如下
src/main/java
**/*.xml
src/main/resources
此时运行还会报错
You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
在db.properties中的url后面添加内容为
?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
然后运行结果:
数据库表中
此时已经成功的用Mybatis书写了一个HelloWorld程序!
本节视频教程:
本节代码源码资料:
2 日志打印
pom.xml文件中引入log4j依赖
log4j
log4j
RELEASE
resources下面创建log4j.properties文件,且里面添加如下内容
log4j.rootLogger= DEBUG,stdout
log4j.logger.org.mybatis=DEBUG
###输出信息到控制抬 ###
log4j.appender.stdout= org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern =%C %p %m %n
此时在运行我们的项目就会发现控制台多出
3 参数传递
BookMapper.xml文件中添加
insert into book set name=#{name},author=#{author};
测试类中
public class test_mybatis {
private SqlSessionsession;
@Before
public void before()throws IOException {
//读取mybatis核心配置文件,抛出异常
InputStream r = Resources.getResourceAsStream("mybatis-conf.xml");
//创建sqlSession工厂
SqlSessionFactory sqf =new SqlSessionFactoryBuilder().build(r);
//通过工厂来获取session对象
session = sqf.openSession();
//通过session来进行响应的crud操作
}
@After
public void after(){
session.close();
}
@Test
public void test1(){
Book book =new Book();
book.setName("浅谈mybatis");
book.setAuthor("孤芳不自赏");
int i =session.insert("addBook",book);
session.commit();
if (i>0) {
System.out.println("添加成功");
}else{
System.out.println("添加失败");
}
}
}
4 自定义Dao方法
之前当我们想实现crud操作时候都是调用seesion里面的方法,现在我们自己来创建自己的方法。
在BookMapper中添加接口
public interface BookMapper {
int addBook(Book book);
int updateBook(Book book);
int deleteBook();
}
此时我们在测试类里面就可以调用自己的接口了
BookMapper.xml文件中添加更新方法
update book set author=#{author} where id=#{id}
测试类中
@Test
public void test2(){
BookMapper mapper =session.getMapper(BookMapper.class);
Book book =new Book();
book.setId(30);
book.setAuthor("MybatisBoy");
int i = mapper.updateBook(book);
if (i>0) {
System.out.println("更新成功");
}else{
System.out.println("更新失败");
}
session.commit();
}
更新结果为
5 简化mybatis配置
5.1创建mapper.xml模板
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
使用刚刚创建好的模板来创建一个testMapper.xml
声明:还可以用来简化db.properties配置文件哦!自己动手尝试一下吧。
5.2 给类起别名
细心的同学会发现我们写插入语句时候的parameterType类型往往是类的路径,能不能进一步简化呢?答案是当然可以了。
虽然简化了一个类,但是当类多的时候每次都要在配置文件中配置这样依然很繁琐。于是可以这样配置。
每次添加一个mapper.xml都要在核心配置文件中引入,这样依然很麻烦。
6 类型转换
mybatis中基本的类型都能进行转换。加入我想将Java中的一个List集合的数据保存都数据库中varchar类型的字段中那么如何保存呢?
给book表添加字段
实体类中添加字段并且生成set和get方法
自己创建类型转换类并且实现TypeHandler接口
配置类上面要添加注解
@MappedJdbcTypes(JdbcType.VARBINARY)
@MappedTypes(List.class)
public class MyTypeHandlerimplements TypeHandler> {
public void setParameter(PreparedStatement preparedStatement,int i, List strings, JdbcType jdbcType)throws SQLException {
/**
* 在这个里面将List集合中的数据全部处理为字符串
* i 插入的第几个位置
* strings为List中的数据
*/
StringBuffer sb =new StringBuffer();
for (String s : strings) {
sb.append(s).append(",");
}
preparedStatement.setString(i,sb.toString());
}
public List getResult(ResultSet resultSet, String s)throws SQLException {
return null;
}
public List getResult(ResultSet resultSet,int i)throws SQLException {
return null;
}
public List getResult(CallableStatement callableStatement,int i)throws SQLException {
return null;
}
}
mapper.xml文件中添加
insert into book set name=#{name},favorities=#{favorities,typeHandler=com.config.MyTypeHandler}
测试类中
@Test
public void test1(){
List list =new ArrayList();
list.add("Java");
list.add("C");
list.add("C++");
list.add("Objc++");
Book book =new Book();
book.setName("浅谈mybatis");
book.setAuthor("孤芳不自赏");
book.setFavorities(list);
int i =session.insert("addBook",book);
session.commit();
if (i>0) {
System.out.println("添加成功");
}else{
System.out.println("添加失败");
}
}
控制台打印的 sql为
数据库中
我们成功的添加之后那么如何获取?
mapper中添加接口
List
getAllBooks();
mapper.xml文件中查询全部
select* from book;
测试类中书写
@Test
public void test_getAllBooks(){
BookMapper mapper =session.getMapper(BookMapper.class);
List list = mapper.getAllBooks();
if (list !=null) {
for (Book book : list) {
System.out.println(book);
}
}
}
经过一番准备后发现测试结果是这样的.
emmm.....此时心里是不是凉凉~~
好了开始放大招了!!
核心配置类中配置别名
将字符串类打回List原形
此时在执行结果为
7 $和#有啥区别
这个问题相比参加过Java面试的时候都遇到过,下面直接来看
7.1使用$
接口
Book getBookById(Integer id);
Mapper.xml配置文件中
select* from book where id=${id}
测试类为
@Test
public void test_getOneBook(){
BookMapper mapper =session.getMapper(BookMapper.class);
Book book = mapper.getBookById(29);
if (book !=null) {
System.out.println(book);
}
}
}
控制台sql为
此处的Id为Integer类型,假如为String类型
接口中
Book getBookById(String id);
Mapper.xml配置文件中
select* from book where id=${id}
测试类
@Test
public void test_getOneBook(){
BookMapper mapper =session.getMapper(BookMapper.class);
Book book = mapper.getBookById("29");
if (book !=null) {
System.out.println(book);
}
}
}
控制台sql为
emm....貌似没看出来变化啊!!
But~~更该测试类
@Test
public void test_getOneBook(){
BookMapper mapper =session.getMapper(BookMapper.class);
Book book = mapper.getBookById("29 or id in (30,31)");
if (book !=null) {
System.out.println(book);
}
}
再次运行
且不说返回的结果如何获取,是不是发生了sql注入???!sql注入多可怕!!
7.2 使用#
将$更改为#
select* from book where id=#{id}
说了这么就是让大家对#和$又更深的认识,不懂的还可以参考以下博客:Mybatis中的#和$到底有啥区别?
8 传递其他值
之前传递的要么是Integer类型要么是String类型要么是对象类型 。如何传递一个Map对象呢?
添加接口
int insertBook(HashMap map);
测试类中
@Test
public void test_insertOneBook(){
HashMap map =new HashMap();
map.put("name","浅谈JavaEE");
map.put("author","孤芳不自赏");
BookMapper mapper =session.getMapper(BookMapper.class);
int i = mapper.insertBook(map);
session.commit();
}
mapper.xml文件中
insert into book set name=#{name},author=#{author}
控制台中
数据中
如何取出来,接口
HashMap
getBookByIdResultMap(Integer uid);
核心配置文件中
select* from book where id=#{uid}
测试类中
@Test
public void test_insertOneBook(){
// HashMap map = new HashMap();
// map.put("name","浅谈JavaEE");
// map.put("author","孤芳不自赏");
BookMapper mapper =session.getMapper(BookMapper.class);
// int i = mapper.insertBook(map);
// session.commit();
HashMap map = mapper.getBookByIdResultMap(34);
System.out.println(map);
}
}
对比观察一下,map返回的是集合,之前的返回的是对象
{author=孤芳不自赏, name=浅谈JavaEE, id=34} //map集合返回
Book{author=孤芳不自赏, name=浅谈JavaEE, id=34} //之前的返回
9 resultMap
9.1通过起别名解决
问题:当数据库中的字段和java实体类中的字段不对应时候,当自动查询时候就无法映射上,此时可以通过给字段其别名的方式来实现,但是每次都要给字段起别名就很麻烦,可以通过抽出该别名字段的方式,但是使用resultMap能很好的解决这一问题。
创建author表
CREATE TABLE `author` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`author_name` varchar(50) DEFAULT NULL,
`book_name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建实体类(注意实体类的属性名和数据库的字段不对应)
Mapper配置文件中
测试类中
结果有两个为Null
通过起别名解决
9.2通过resultMap解决
配置文件中书写
select* from author;
其中
column对应的是数据库中的字段
property对应的是实体类中的字段
测试结果和上面的一样
9.3 传递多个参数
当我们想存储bookName和AuthorName这两个字段到数据库表中的时候,但是又不想通过传递实体类来封装,此时该怎么办?
接口中
int insertAuthor(String bookName,String authorName);
xml文件中
insert into author set book_name=#{bookName},author_name=#{authorName}
测试
@Test
public void test4(){
AuthorMapper mapper =session.getMapper(AuthorMapper.class);
int i = mapper.insertAuthor("JavaEE","孤芳不自赏");
session.commit();
}
控制台报错
org.apache.ibatis.binding.BindingException: Parameter 'bookName' not found
为了解决这个问题:@Param横空出世
将接口改为
int insertAuthor(@Param("bookName") String bookName, @Param("authorName") String authorName);
此时在运行就可以插入成功了。添加上注解之后就连parameterType也可以不写了。
最终的mapper为
insert into author set book_name=#{bookName},author_name=#{authorName}
@Param的意思就是将bookName注入到mybatis中,当在配置文件中使用的时候,这个名字是认识的,所以就不会报找不到该名字。
10 动态sql
在mysql中个人感觉使用频率最高的还是动态sql,动态sql顾名思义,就是sql语句是可以动态的改变的呗。稍微好听的称呼是:动态sql就是可以在sql语句中拼接或组装sql。
>>foreach
10.1使用foreach根据传递数组id集合进行查询
添加接口
List getAuthorByIds(@Param("ids") Long[] id);
配置文件中
select* from author where id in(
#{id}
)
测试类中
@Test
public void test5(){
AuthorMapper mapper =session.getMapper(AuthorMapper.class);
Long[] ids={1L,5L,6L};
List list = mapper.getAuthorByIds(ids);
for (Author author : list) {
System.out.println(author);
}
}
测试结果
语句可以进一步的简化
select* from author where id in
#{id}
如果不起别名的话报错如下
更改为array即可
根据传递id的List集合进行查询。
使用list或者collection
测试类
10.2 使用foreach实现批量添加
myqsl数据库中批量添加的方式为
insert into author(author_name,book_name) VALUES('李白','静夜思'),('李白','蜀道难')
接口
int insertManyAuthor( List authors);
配置文件,注意这个没有close和open属性
insert into author(book_name,author_name) value
(#{authors.bookName},#{authors.authorName})
测试类
@Test
public void insertManyAuthor(){
AuthorMapper mapper =session.getMapper(AuthorMapper.class);
ArrayList list =new ArrayList();
list.add(new Author("冰心","寄小读者"));
list.add(new Author("滕王阁序","王勃"));
int i = mapper.insertManyAuthor(list);
session.commit();
if (i>0) {
System.out.println("插入成功");
}else {
System.out.println("插入失败");
}
}
结果
>>if
10.3 if语句
if语句常用来判断插入的值是否为空
如之前的根据ids来查询结果
But当ids为null时候,就会报错
List list = mapper.getAuthorByIds(null);
Caused by: org.apache.ibatis.builder.BuilderException: The expression 'ids' evaluated to a null value.
之前的sql语句是这样的
select* from author where id in
#{id}
修改后的sql语句是这样的
select* from author
where id in
#{id}
当ids为空时候打印的sql是,即查询全部
Preparing: select * from author
>>where
10.4 where语句
where和if是一对好基友,经常在一起搭配使用,例如查询一个id大于4且名字带有“李”的所有的用户。
接口
List getAuthorsBySelect(@Param("id") Integer id,@Param("name") String name);
配置文件中
select* from author
and id>#{id}
and author_name like concat('%',#{name},'%')
测试类
@Test
public void testSelect(){
AuthorMapper mapper =session.getMapper(AuthorMapper.class);
List authors = mapper.getAuthorsBySelect(4,"李");
for (Author author : authors) {
System.out.println(author);
}
}
结果
当参数为空时候查询所有
借此机会给大家普及一下where里面的And的使用 ,一直匹配,直到遇到第一个条件满足时候将前面的and去掉。
>>set
10.5 set集合
接口
int updateAuthor(Author author);
配置文件
update author
author_name=#{authorName},
book_name=#{bookName},
where id=#{id}
测试类
@Test
public void testUpdate(){
AuthorMapper mapper =session.getMapper(AuthorMapper.class);
Author author =new Author();
author.setId(9);
author.setBookName("冰心散文");
author.setAuthorName("冰心");
int i = mapper.updateAuthor(author);
session.commit();
if (i>0) {
System.out.println("更新成功");
}else{
System.out.println("更新失败");
}
}
结果
借此机会给大家普及一下set里面的逗号用 ,一直匹配,将最后一个符合条件的逗号去掉。
11 主键回填
11.1当id设置成自增长时
当向数据库中插入一个字段时候,将插入的所有信息返回包括id。
将刚刚的配置文件内容更改为
useGeneratedKeys="true" keyProperty="id" keyColumn="id"> update author
author_name=#{authorName},
book_name=#{bookName},
where id=#{id}
测试类中添加一条语句即可
System.out.println("主键回填>>>>>>>>>>"+author);
测试运行结果为
11.2当id不为自增长且为varchar类型时
创建一个Cat表
CREATE TABLE `cat` (
`id` varchar(255) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建实体类
public class Cat {
private Stringid;
private Stringname;
}
配置文件中,注意返回值类型是java.lang.String
resultType="java.lang.String"> select uuid();
insert into cat set id=#{id},name=#{name}
测试类
@Test
public void testCat(){
CatMapper mapper =session.getMapper(CatMapper.class);
Cat cat =new Cat();
cat.setName("小花");
mapper.insertCat(cat);
session.commit();
System.out.println(cat);
}
结果
12 一对一查询
创建员工表和部门表两个,一个员工属于一个部门
部门表
CREATE TABLE `department` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`dname` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
员工表,在员工表里面有一个部门表中的id作为字段以此来证明该员工属于那个部门
CREATE TABLE `employee` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`sex` varchar(10) DEFAULT NULL,
`age` int(10) DEFAULT NULL,
`phone` varchar(50) DEFAULT NULL,
`address` varchar(50) DEFAULT NULL,
`did` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
员工表和部门表之间的关系
创建部门表实体类
public class Department {
private Integerid;
private Stringname;
}
创建员工表实体类,并且员工表中包含部门类属性
public class Employee {
private Integerid;
private Stringname;
private Stringsex;
private Integerage;
private Stringphone;
private Stringaddress;
private Integerdid;
private Departmentdepartment;
}
部门表中添加内容
员工表
接口
List
getAllEmp();
配置文件
select e.*,d.* from employee e,department d where e.did=d.id
测试类
@Test
public void testEmp(){
EmployeeMapper mapper =session.getMapper(EmployeeMapper.class);
List list = mapper.getAllEmp();
for (Employee employee : list) {
System.out.println(employee);
}
}
结果
解决办法:自己定义返回类型
配置文件中
select e.*,d.id as depId,d.dname as depName from employee e,department d where e.did=d.id
配置文件解释
结果
13 一对多
员工和角色是一对多的关系
创建员工表
CREATE TABLE `employee` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`sex` varchar(10) DEFAULT NULL,
`age` int(10) DEFAULT NULL,
`phone` varchar(50) DEFAULT NULL,
`address` varchar(50) DEFAULT NULL,
`did` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
创建角色表
CREATE TABLE `role` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`rname` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
创建员工角色表
CREATE TABLE `e_r` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`eid` int(10) DEFAULT NULL,
`rid` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
创建部门表
CREATE TABLE `department` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`dname` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
员工表中的内容
角色表中的内容
员工角色表中的内容
部门表中的内容
接口
List
getAllEmpAndRoles();
配置文件
select e.*,d.id as depId,d.dname as depName from employee e,department d where e.did=d.id
select e.*,er.*,r.id as rid,GROUP_CONCAT(r.rname) as rName,d.id as depId,d.dname as depName from employee e,e_r er,department d, role r
where e.did=d.id and er.eid=e.id and er.rid=r.id
GROUP BY e.id
测试类
@Test
public void testEmp(){
EmployeeMapper mapper =session.getMapper(EmployeeMapper.class);
List list = mapper.getAllEmpAndRoles();
for (Employee employee : list) {
System.out.println(employee);
}
}
结果
问题:
如果查询结果是一下这样的,如何将rName合并成上图
使用GROUP_CONCAT(要合并的字段)...... GROUP BY 用户表.id
14 延迟加载
11.1一对迟加载
什么是迟加载?举个栗子来说了吧。
员工表中的属性
public class Employee {
private Integerid;
private Stringname;
private Stringsex;
private Integerage;
private Stringphone;
private Stringaddress;
private Integerdid;
private Department department;
private List
roles; }
当我只想获取员工表中的基本字段(除了)department和roles
接口中,注意员工接口中声明部门属性。
public interface EmployeeMapper {
List getAllEmp();
List getAllEmp(Integer id);
List getAllEmpAndRoles();
Department getEepById(Integer did);
}
配置文件中
测试类
结果
添加懒加载
结果
当想获取Department属性时候在加载
测试类中
结果
11.2一对多迟加载
接口中
List
getRoles(Integer id);
配置文件中
测试文件中
结果
12 缓存
mybatis一级缓存即查询缓存
查询两次只执行一次查询语句
一级缓存的作用域是session,session结束后缓存停止。
二级缓存的作用域是sqlSession