Mybatis-CRUD
上一篇mybatis入门中已经讲了如何搭建Mybatis的环境,主要是pom.xml依赖,SQLMapConfig.xml主配置文件以及dao接口和数据库对应实现类的编写,如果是使用xml配置方式,则需要对dao接口编写指定xml文件(路径也要相同),相反,注解方式则不需要。这里就不讲如何进行环境搭建的具体流程了,有需要的可以去看看。
数据库相关信息以及对应类相关信息
create table user(
id int not null primary key auto_increment,
username varchar(32) not null comment '用户名称',
birthday datetime default null comment '生日',
sex char(1) default null comment '性别',
address varchar(256) default null comment '地址'
);
insert into user(username,birthday,sex,address)
values('嘿嘿','2000-00-00','1','1'),
('哈哈','2010-03-16','2','2');
package org.example.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.util.Date;
@Getter
@Setter
@ToString
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
package org.example.dao;
import org.example.domain.User;
public interface IUserDao {
}
查询全部
这里使用的是xml注解的方式,所以要实现查询全部就得先对IUserDao接口配置对应的xml文件,以及在dao接口文件中写好查询方法。
package org.example.dao;
import org.example.domain.User;
import java.util.List;
public interface IUserDao {
//查询所有的方法
List<User> findAll();
}
<?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.example.dao.IUserDao">
<select id="findAll" resultType="org.example.domain.User">
select * from user;
</select>
</mapper>
注:namespace+id可以确认是哪个dao接口的那个方法,resultType可以确认返回类型,进行数据封装。
package org.example.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.example.dao.IUserDao;
import org.example.domain.User;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* myBatis增删改查测试
*/
public class MybatisTest {
@Test
public void findAll() throws IOException {
//1.读取配置文件信息
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.创建SqlSession对象
SqlSession session = factory.openSession();
//4.创建动态代理对象
IUserDao user = session.getMapper(IUserDao.class);
List<User> userList = user.findAll();
for(User u : userList){
System.out.println(u);
}
session.close();
in.close();
}
}
这里有很多代码后序还是会用到,所以可以抽出来,增删改查不同的地方就是代理对象调用不同的方法和对结果集的处理,前边读取配置文件信息、创建SqlSessionFactory工厂、创建SqlSession对象、创建动态代理对象都是一样的,所以我们可以把它们抽成init方法,并利用Junit框架@Before注解起到在测试代码执行前执行的效果,同样的关闭资源操作也可以抽取出来封装成destroy方法,利用@After注解在测试代码执行之后执行。
package org.example.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.example.dao.IUserDao;
import org.example.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.List;
/**
* myBatis增删改查测试
*/
public class MybatisTest {
private InputStream in;
private SqlSession session;
private IUserDao userDao;
@Before
public void init() throws Exception {
//1.读取配置文件信息
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.创建SqlSession对象
session = factory.openSession();
//4.创建动态代理对象
userDao = session.getMapper(IUserDao.class);
}
@After
public void destroy() throws IOException {
session.close();
in.close();
}
@Test
public void findAll() throws IOException {
List<User> userList = userDao.findAll();
for(User u : userList){
System.out.println(u);
}
}
}
这样的代码看起来更加清楚明了,并且后序其他操作,使用使用起来也是更简便了。
添加操作
首先需要在IUserDao接口中添加方法声明。
//添加数据
void insertUser(User user);
然后配置IUserDao.xml中的映射,在之前写好的mapper标签里面继续添加insert标签(与刚才写的select同级),id要与声明方法名称相同,以前在学习jdbc写插入语句的时候可以使用占位符,但是现在得要用#{列名},并且要说明参数类型是什么,也就是parameterType="…"。
<insert id="insertUser" parameterType="org.example.domain.User">
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>
最后在测试方法中定义需要插入的user对象,将信息都设置完成,id是自增长可以不用设置。使用代理对象调用insertUser方法,并将定义好的对象传入。这里有两点需要说明:1.Mybatis默认的提交方式是false,所以我们可以手动提交。2.存入数据库中的数据可能乱码,这里可以给数据库连接信息url后面加上characterEncoding=UTF-8信息(以’?'连接前面),从而解决乱码问题。
@Test
public void insertUser() throws IOException {
User user = new User();
user.setUsername("李华");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("北京市延庆县");
userDao.insertUser(user);
session.commit();
}
注:提交操作其实只要在关闭资源之前执行就可以,所以可以把它一并放到destroy方法中。
修改操作
IUserDao接口声明方法。
//修改数据
void updateUser(User user);
IUserDao.xml中写update标签、idparameterType、SQL语句
<update id="updateUser" parameterType="org.example.domain.User">
update user set username=#{username},sex=#{sex} where id=#{id}
</update>
进行测试
@Test
public void updateUser(){
User user = new User();
user.setId(5);
user.setUsername("小王");
user.setSex("女");
userDao.updateUser(user);
}
删除操作
都是老套路,唯一要说明的是删除的parameterType可以写成INT或者java.lang.Integer或org.example.domain.User,原因后序说明。
//删除数据
void deleteUser(Integer userId);
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{uid}
</delete>
@Test
public void deleteUser(){
userDao.deleteUser(10);
}
查询单条记录
查询单挑记录那么就是有条件的查询,并且是有返回类型的,所有需要同时配置resultType和ParameterType。
//查询单条数据
User findOne(Integer userId);
<select id="findOne" resultType="org.example.domain.User" parameterType="java.lang.Integer">
select * from user where id = #{uid}
</select>
@Test
public void findOne(){
User u = userDao.findOne(1);
System.out.println(u);
}
模糊查询
模糊查询其实和上面的没啥区别,只不过要注意,在传入参数的时候,一定要按照模糊查询的格式来写。
//模糊查询
List<User> findByName(String uName);
<select id="findByName" resultType="org.example.domain.User" parameterType="String">
select * from user where username like #{name}
</select>
@Test
public void findByName(){
List<User> users = userDao.findByName("%李%");
for(User u : users){
System.out.println(u);
}
}
聚合函数
聚合函数查询也是支持的。
//聚合函数,查询总个数
int countAll();
<select id="countAll" resultType="int">
select count(*) from user;
</select>
@Test
public void countAll(){
int count = userDao.countAll();
System.out.println(count);
}
配置文件补充
为实现开发更加简便,在配置文件中还提供了很多的标签,现在主要介绍properties(属性)和typeAliases(类型别名)。
properties
在主配置文件中,我们配置了数据库的连接信息,把它们放在了dataSource标签里面,这里我们可以利用properties标签将其放在单独的properties中,如下:
注:单独放好的properties需要在使用的地方,value值用${}来填写properties的name,这就好像是一个链接标注了存储它的位置一样,但是这样做感觉有点多余了,其实真正做它的意义在于解耦,所以可以将这些信息单独的放在properties文件中,此时,我们在resources文件中创建jdbc.properties文件,并将内容填写进去。
resource
单独写properties文件,该如何去使用呢?这时候就需要用到resource属性了,resource属性用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。按刚才直接写在resources文件路径的方式,就可以在resource的值中直接填写文件全名来标注了。此时还要注意,使用这种方式时,文件中的格式要value中的格式保持一致,否则无法找到。
typeAliases
在写resultType或者parameterType的时候,有没有发现int、Java.lang.Integer这些都是可以随便写的,但是我们自定义的类却没有办法随便写,这是因为mybatis中给前者起了别名,所以可以使用,那么这时候我们也可以使用typeAliases来给自己的类起别名,使代码更加简洁。
注:type表示原全限定类名,alias表示需要起的别名。并且起了别名之后,将不再区分大小写,请注意下面我故意将写成不同的大小写格式,但是都可以正常使用。
package
在正式项目中一定会存在非常多的类,这时候如果我们一一进行取别名,那么就会相当麻烦,此时我们可以使用package,它的作用就是指定要配置别名的包,指定后,该包下的实体类都会注册别名,并且类名就是别名,同样不再区分大小写。
package还可以用在mappers标签下,之前我们在mappers标签下使用的mapper,并用resource标识dao接口xml配置文件路径,而使用package之后就可以将指定包下的配置文件全部映射。
mybatis基于XML配置的动态SQL语句使用
根据情况不同,我们会做不同的SQL来进行查询,有可能有的条件有,而有的条件没有,这种情况在多条件组合查询中经常会碰到。下面我们来做一个条件查询,有可能有性别,也有可能有地址,还有可能都有,这时候就要用到< if > 标签了。(其实主要的步骤和之前的CRUD都是一样的,只不过在dao接口xml配置中多了动态配置。
首先在dao接口中声明方法
这里直接将User类作为参数传入,根据我们的动态判断,会自动寻找传入对象的属性中是否匹配我们配置的条件。
//根据条件进行查询
List<User> findUserByCondition(User user);
< if > 标签单独使用时,需要在where后面用1=1的恒等式来衔接,并在test属性中插入我们需要的判断语句,此处需要注意的是,属于SQL语句部分的是不区分大小写的,而属于Java部分的则是严格按照格式来的,不匹配则会出错。所以一般情况下,我们建议将映射的这些对应关系都尽可能保持一致,防止出错。
<select id="findUserByCondition" resultType="user" parameterType="user">
select * from user where 1=1
<if test="username != null">
and username=#{username}
</if>
<if test="id != null">
and id=#{id}
</if>
</select>
上面设置了判断的条件有两个:1.username是否为空。2.id是否为空。也就是可以两个条件都没有,即没有后缀判断;两个条件有任意一个,即按有的那个条件查询;两个条件都有,即按照两个传入的属性值进行查询。
@Test
public void findUserByCondition(){
User user = new User();
user.setUsername("李华");
List<User> users = userDao.findUserByCondition(user);
for(User u : users){
System.out.println(u);
}
}
where
刚才使用if标签的时候在where后面加上1=1恒等式,< where > 标签可以将if包裹进去,这样就不用加恒等式了。
<select id="findUserByCondition1" resultType="user" parameterType="user">
select * from user
<where>
<if test="username != null">
username=#{username}
</if>
<if test="id != null">
and id=#{id}
</if>
</where>
</select>
和刚才的示例起到相同的作用。
foreach
讲foreach标签之前,先说一下,我们的之前所用的都是User类直接包装的作为我们的参数,然而SQL语句的条件是很多种的,比方说我们要查询User.id为{1,2,3}这几个id,这时候可能会用到in,那么如果只使用一个User类显然是无法封装的,所以可以将一些我们所需要的条件封装在一个自定义类中,然后作为参数传入。
package org.example.domain;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
public class QueryVo {
//假如我们需要使用到user中的一些属性,还有List集合,现在将它们俩一起封装起来
private User user;
private List<Integer> list;
}
查询id为{1,2,3}的User,首先声明方法。
//foreach查询
List<User> findUserByIds(QueryVo queryVo);
配置xml文件,这里我们要用到queryVo这个类,此时要注意,因为之前我们已经配置了typeAliases标签中的package(org.example.domain),QueryVo这个类也下该包下,所以可以直接使用别名(不区分大小写)。foreach可以和if做搭配使用,collection表示我们要遍历的集合,open表示where后面要加入的SQL语句,close表示SQL最后的结尾,item则表示每一个元素,separator则表示分隔符,所以就是将字符串进行拼接,并把集合中的元素放入,最后组成SQL语句。
<select id="findUserByIds" resultType="user" parameterType="queryvo">
select * from user
<where>
<if test="ids != null and ids.size()>0">
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
@Test
public void findUserByIds(){
QueryVo q = new QueryVo();
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
q.setIds(list);
List<User> users = userDao.findUserByIds(q);
for(User u : users){
System.out.println(u);
}
}
补充
之前用到很多的SQL语句其实都是重复的,那么可以使用< sql > 标签来将其抽取出来,用id属性来表示,最后在对应的配置中进行引用即可。
<sql id="defaultUser">
select * from user
</sql>
<select id="findAll" resultType="user">
<include refid="defaultUser"></include>
</select>
mybatis中的多表查询
多表查询分为:一对一,一对多(多对一),多对多三种情况。举例:一个用户可能有多个账户,也就是一对多的关系,此时应该给账户表中的userId设为外键,保证它们的关联性,下面我们先来创表并构建实体类,以及相关配置。
create table account(
ID int(11) primary key comment '编号',
UID int(11) default null comment '用户编号',
MONEY double default null comment '金额',
foreign key (UID) references user(id)
);
insert into account(ID,UID,MONEY) values (1,1,1000),(2,2,1000),(3,3,2000);
注:之前已经创过User表了,所以这里直接创建了account表即可。
account表的dao接口
package org.example.dao;
import org.example.domain.Account;
import java.util.List;
public interface IAccountDao {
//查询所有账户
List<Account> findAll();
}
account实体类
package org.example.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class Account {
private Integer id;
private Integer uid;
private Double money;
}
IAccountDao.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="org.example.dao.IAccountDao">
<select id="findAll" resultType="account">
select * from account
</select>
</mapper>
测试:
package org.example.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.example.dao.IAccountDao;
import org.example.domain.Account;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* myBatis增删改查测试
*/
public class AccountTest {
private InputStream in;
private SqlSession session;
private IAccountDao accountDao;
@Before
public void init() throws Exception {
//1.读取配置文件信息
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.创建SqlSession对象
session = factory.openSession();
//4.创建动态代理对象
accountDao = session.getMapper(IAccountDao.class);
}
@After
public void destroy() throws IOException {
session.commit();
session.close();
in.close();
}
@Test
public void testFindAll(){
List<Account> accounts = accountDao.findAll();
for(Account account : accounts){
System.out.println(account);
}
}
一对一
接下来就来写一下多表查询,查询所有账户,并且带有用户名称和地址信息。因为我们需要查的是多张表中的信息,所以此时无法用之前写过的实体类来封装了,那么这时候就可以单独去实现一个实体类,继承Account类,并添加username和address属性。
package org.example.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString(callSuper = true)
public class AccountUser extends Account{
private String username;
private String address;
}
接下来就是老套路了,声明方法
//查询所有账户,并且带有用户名称和地址信息
List<AccountUser> findAllAccount();
IAccountDao.xml
<select id="findAllAccount" resultType="accountuser">
select a.*,u.username,u.address from account a,user u where u.id=a.uid;
</select>
测试
@Test
public void testFindAllAccount(){
List<AccountUser> aus = accountDao.findAllAccount();
for(AccountUser au : aus){
System.out.println(au);
}
}
上面这种方式急需要单独定义一个类来封装,其实可以不用这样,而是将User直接封装到Account里即可,因为一个账户一定只属于一个用户,所以相当于利用起了一对一的关系。
package org.example.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class Account {
private Integer id;
private Integer uid;
private Double money;
//从表实体应该包含一个主表实体的对象引用
private User user;
}
那么问题来了,如果是这样进行封装,resultType该怎么写呢?这时候就需要用到resultMap了,定义一个封装account和user的resultMap。
<?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.example.dao.IAccountDao">
<!--定义封装account和user的resultMap-->
<resultMap id="accountUserMap" type="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--一对一的关系映射,配置封装user的内容-->
<association property="user" column="uid">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
</association>
</resultMap>
<select id="findAll" resultMap="accountUserMap">
select u.*,a.id aid,a.uid,a.money from account a,user u where u.id=a.uid
</select>
<select id="findAllAccount" resultType="accountuser">
select a.*,u.username,u.address from account a,user u where u.id=a.uid;
</select>
</mapper>
一对多
用户可以拥有多个账户是一对多关系,那么如何进行查询,使得这种一对多的关系展现出来呢?很显然,我们是主要针对用户的,所以可以在User类(主表)将List< Account >(从表,一个用户有多个账户)属性放入。
User类加入属性
//一对多关系映射,主表实体应该包含从表实体的集合引用
private List<Account> accounts;
配置IUserDao.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="org.example.dao.IUserDao">
<!--定义User的resultMap-->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<!--配置user对象中accounts集合的映射-->
<collection property="accounts" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userAccountMap">
select * from user u left join account a on u.id=a.uid
</select>
</mapper>
这里是对应的多表查询,可以使用resultMap。collection标签标示属性是一个集合,并将其对应的id和result写清,注意property是Java中对应名称,而column是SQL语句中对应的。
测试
@Test
public void findAll() throws IOException {
List<User> userList = userDao.findAll();
for(User u : userList){
System.out.println(u);
}
}
多对多
多对多关系举例:一个用户可能具有多个身份(角色),一个角色可能包含多个用户。站在数据库中的角度,如果想要表示它们之间多对多的关系就需要有中间表来搭建,并且中间表中包含各自的主键作为它的外键。现在我们来实现查询用户时,可以同时得到用户所包含的角色信息,查询角色时,可以同时得到角色所赋予的用户信息。
数据库建表
create table role(
id int(11) primary key comment '编号',
role_name varchar(30) default null comment '角色名称',
role_desc varchar(60) default null comment '角色描述'
);
insert into role(id,role_name,role_desc) values(1,'院长','管理整个学院'),(2,'总裁','管理整个公司'),(3,'学生','在学校乖乖学习');
create table user_role(
id int(11) primary key auto_increment,
uid int(11) comment '用户编号',
rid int(11) comment '角色编号',
foreign key(uid) references user(id),
foreign key(rid) references role(id)
);
insert into user_role(uid,rid) values (1,1),(2,1),(1,2);
创建实现类
一会我们进行查询角色并显示对应用户的操作,所以可以将User类装成一个List集合放入Role实现类中作为属性。
package org.example.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
@Getter
@Setter
@ToString
public class Role {
private Integer roleId;
private String roleName;
private String roleDesc;
//多对多的关系映射,一个角色可以赋予多个用户
private List<User> users;
}
Dao接口
package org.example.dao;
import org.example.domain.Role;
import java.util.List;
public interface IRoleDao {
List<Role> findAll();
}
IRoleDao.xml
需要进行的操作是查询所有角色,并显示角色中包含的用户,这时候需要创建resultMap,并写好SQL语句,写SQL语句的时候要注意,因为一行比较长,所以进行了换行,需要对换行位置添加空格,防止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="org.example.dao.IRoleDao">
<!--定义role表的ResultMap-->
<resultMap id="roleMap" type="role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
<collection property="users" ofType="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
</collection>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="roleMap">
select u.*,r.id rid,r.role_name,r.role_desc from role r
left join user_role ur on r.id = ur.rid
left join user u on u.id = ur.uid;
</select>
</mapper>
测试
@Test
public void findAll() throws IOException {
List<Role> roles = roleDao.findAll();
for(Role r:roles){
System.out.println(r);
}
}