本教程源码请访问:tutorial_demo
通过前面的学习,我们对MyBatis的概念和环境搭建已经有了一个基本的了解,接下来我们学习一下MyBatis的CRUD。这篇教程的环境要求如下:
- 持久层接口和持久层接口的映射配置文件必须在相同的包下;
- 持久层接口的映射配置文件mapper标签的namespace属性取值必须是持久层接口的全限定类名;
- 持久层接口的映射配置文件SQL语句标签的属性必须和持久层接口的方法名相同。
注意:这篇教程是“02_MyBatis快速入门”的延续,环境和02当中完全相同,如果02没有配置运行成功,请返回认真学习。
一、根据id查询
1.1、在持久层接口IUserDao中添加相应方法
//根据id查询
User findById(Integer id);
1.2、在持久层接口的映射配置文件IUserDao.xml添加配置
<select id="findById" parameterType="java.lang.Integer" resultType="org.codeaction.domain.User">
select * from user where id=#{id}
</select>
resultType属性:用于指定结果集的类型;
parameterType属性:用于指定传入参数的类型;
#{}:它代表占位符, 相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据,具体的数据是由#{}里面的内容决定的,由于数 据类型是基本类型,所以此处可以随意写。
1.3、修改测试类MyBatisTest
package org.codeaction.test;
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.codeaction.dao.IUserDao;
import org.codeaction.domain.User;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
public class MyBatisTest {
private InputStream in;
private SqlSession session;
/**
*
* 在所有测试方法运行之前运行,进行初始化操作
* @throws IOException
*/
@Before
public void init() throws IOException {
//读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//使用工厂生产SqlSession对象
session = factory.openSession();
}
/**
* 在所有测试方法运行之后运行,进行清理操作
* @throws IOException
*/
@After
public void destroy() throws IOException {
//提交事务
session.commit();
//释放资源
session.close();
in.close();
}
@Test
public void testFindAll() throws IOException {
//使用SqlSession创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//使用代理对象执行方法
List<User> list = userDao.findAll();
list.forEach(System.out::println);
}
@Test
public void testFindById() {
IUserDao userDao = session.getMapper(IUserDao.class);
User user = userDao.findById(41);
System.out.println(user);
}
}
由于我们后面要写多个测试方法,每个测试方法中有一些步骤是完全相同的,那么我们把重复的操作抽取出来,进行了如下操作:
-
创建了init()方法,进行初始化操作,并添加@Before注解,从而init()方法可以在任意一个被测试方法运行之前运行;
-
创建了destroy()方法,进行销毁操作,并添加@After注解,从而destroy()方法可以在任意一个被测试方法运行之后运行。
运行testFindById()方法,结果如下:
User{id=41, username='王一', birthday=Tue Dec 27 17:47:08 CST 2011, sex='男', address='北京'}
二、添加操作
2.1、在持久层接口IUserDao中添加相应方法
//添加用户
Integer add(User user);
2.2、在持久层接口的映射配置文件IUserDao.xml添加配置
<insert id="add" parameterType="org.codeaction.domain.User">
insert into user(username, birthday, sex, address) values(#{username}, #{birthday}, #{sex}, #{address})
</insert>
parameterType属性:代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称;
#{}:它代表占位符,相当于原来JDBC部分所学的?,都是用于执行语句时替换实际的数据,具体的数据是由#{}里面的内容决定的;
#{}中内容的写法:由于我们保存方法的参数是一个User对象,此处要写User对象中的属性名称,它用的是OGNL表达式;
OGNL表达式:Object Graphic Navigation Language(对象图导航语言)它是Apache提供的一种表达式语言,它是按照一定的语法格式来 获取数据。语法格式就是使用#{对象.对象}的方式,#{user.username}它会先去找user对象,然后在user对象中找到username属性,并调 用getUsername()方法把值取出来,但是我们在parameterType属性上指定了实体类名称,所以可以省略user.而直接写username。
2.3、在测试类MyBatisTest添加测试方法
@Test
public void testAdd() {
User user = new User();
user.setUsername("张三");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("南昌");
IUserDao userDao = session.getMapper(IUserDao.class);
System.out.println(result);
}
注意:测试类destroy()方法中一定要有“提交事务”(上面的代码已经添加)操作,否则添加、删除、修改不会成功,但是会回滚。
运行add()方法,在控制台打印“1”,并且数据库中添加了相应的记录,证明添加成功。
2.4、返回新增用户id
新增用户后,同时还要返回当前新增用户的id值,因为id是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment 的值返回。
修改持久层接口的映射配置文件IUserDao.xml
<insert id="add" parameterType="org.codeaction.domain.User">
<selectKey keyProperty="id" keyColumn="id" resultType="java.lang.Integer" order="AFTER">
select last_insert_id()
</selectKey>
insert into user(username, birthday, sex, address) values(#{username}, #{birthday}, #{sex}, #{address})
</insert>
keyProperty:selectKey语句结果应该被设置到的目标属性;
keyColumn:主键对应的列名;
resultType:结果的类型;
order:可以设置为BEFORE或AFTER。如果设置为BEFORE,那么它首先会生成主键,设置keyProperty再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是selectKey中的语句,MySQL中设置为AFTER就可以。
修改测试类中testAdd()方法
@Test
public void testAdd() {
User user = new User();
user.setUsername("张三");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("南昌");
IUserDao userDao = session.getMapper(IUserDao.class);
Integer result = userDao.add(user);
//打印User对象,以便于我们观察是否生成了id
System.out.println(user);
System.out.println(result);
}
运行add()方法,控制台输出如下:
User{id=58, username='张三', birthday=Fri May 15 18:50:03 CST 2020, sex='男', address='南昌'}
1
结果中id存在值,证明返回了新增用户的id。
三、更新操作
3.1、在持久层接口IUserDao中添加相应方法
//更新用户
Integer chg(User user);
3.2、在持久层接口的映射配置文件IUserDao.xml添加配置
<update id="chg" parameterType="org.codeaction.domain.User">
update user set username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} where id=#{id}
</update>
3.3、在测试类MyBatisTest添加测试方法
@Test
public void testChg() {
User user = new User();
user.setId(58);
user.setUsername("张三");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("长沙");
IUserDao userDao = session.getMapper(IUserDao.class);
Integer result = userDao.chg(user);
System.out.println(result);
}
四、删除操作
4.1、在持久层接口IUserDao中添加相应方法
Integer del(Integer id);
4.2、在持久层接口的映射配置文件IUserDao.xml添加配置
<delete id="del" parameterType="java.lang.Integer">
delete from user where id=#{id}
</delete>
4.3、在测试类MyBatisTest添加测试方法
@Test
public void testDel() {
IUserDao userDao = session.getMapper(IUserDao.class);
Integer result = userDao.del(58);
System.out.println(result);
}
五、使用聚合函数查询
5.1、在持久层接口IUserDao中添加相应方法
Integer findTotal();
5.2、在持久层接口的映射配置文件IUserDao.xml添加配置
<select id="findTotal" resultType="java.lang.Integer">
select count(id) from user
</select>
5.3、在测试类MyBatisTest添加测试方法
@Test
public void testFindTotal() {
IUserDao userDao = session.getMapper(IUserDao.class);
Integer count = userDao.findTotal();
System.out.println(count);
}
六、模糊查询
需求:查询所有名字中带”王“的User。
6.1、在持久层接口IUserDao中添加相应方法
List<User> findByName(String username);
6.2、在持久层接口的映射配置文件IUserDao.xml添加配置
<select id="findByName" parameterType="java.lang.String" resultType="org.codeaction.domain.User">
select * from user where username like #{username}
</select>
6.3、在测试类MyBatisTest添加测试方法
@Test
public void testFindByName() throws Exception{
IUserDao userDao = session.getMapper(IUserDao.class);
List<User> users = userDao.findByName("%王%");
users.forEach(System.out::println);
}
运行测试方法,部分控制台信息如下:
==> Preparing: select * from user where username like ?
==> Parameters: %王%(String)
Total: 3
User{id=41, username='王一', birthday=Tue Dec 27 17:47:08 CST 2011, sex='男', address='北京'}
User{id=42, username='王二', birthday=Sat Mar 12 15:09:37 CST 2011, sex='女', address='上海'}
User{id=46, username='老王', birthday=Sat Aug 07 17:37:26 CST 1999, sex='女', address='拉萨'}
#{username}被MyBatis当成了一个整体来处理,编译之后当成了一个占位符。
6.4 模糊查询的另一种配置
6.4.1、持久层接口的映射配置文件修改
<select id="findByName" parameterType="java.lang.String" resultType="org.codeaction.domain.User">
select * from user where username like '%${value}%'
</select>
6.4.2、测试方法修改
@Test
public void test11FindByName() throws Exception{
IUserDao userDao = session.getMapper(IUserDao.class);
List<User> users = userDao.findByName("王");
users.forEach(System.out::println);
}
运行测试方法,部分控制台信息如下:
==> Preparing: select * from user where username like '%王%'
==> Parameters:
Total: 3
User{id=41, username='王一', birthday=Tue Dec 27 17:47:08 CST 2011, sex='男', address='北京'}
User{id=42, username='王二', birthday=Sat Mar 12 15:09:37 CST 2011, sex='女', address='上海'}
User{id=46, username='老王', birthday=Sat Aug 07 17:37:26 CST 1999, sex='女', address='拉萨'}
通过对比可以发现,程序代码中不需要加入模糊查询的匹配符%了 ,执行结果是一样的,但是执行的语句不一样。
6.5、#{}与${}的区别
#{}表示占位符
通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止SQL注入。#{}可以接收简单类型值或pojo属性值。
${}表示拼接SQL
通过${}可以将parameterType传入的内容拼接在SQL中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值。