MyBatis与简单增删改查
selcet
在我们之前说过的权限系统中,我们需要使用到查询功能来查询用户、角色和权限等数据。在使用纯粹的JDBC时,需要写查询语句,并且对结果集进行手动处理,将结果集映射到对象的属性中。使用MyBatis时,只需要在XML中添加一个select元素,写一个SQL,再做一些简单的配置,就可以将结果直接映射到对象中。总结一下操作方法:
- 在接口类中写查询的方法,常见的如selectById()、selectAll()等
- 在与接口类对应的XML文件中写SQL语句
- 写测试类文件进行接口中方法的测试
接下来我们按照上述过程先写一个简单的查询测试一下。我们写一个按照用户ID查询用户信息的select语句吧。
首先,在UserMapper接口类文件中写一个查询方法:
package Interface;
import pojo.User;
public interface UserMapper {
//根据ID查找用户
User selectById(Long id);
}
然后我们在与UserMapper接口对应的UserMapper.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="Interface.UserMapper">
<!--根据用户ID查找用户-->
<resultMap id="userMap" type="pojo.User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="userPassword" column="user_password"/>
<result property="userEmail" column="user_email"/>
<result property="userInfo" column="user_info"/>
<result property="headImg" column="head_Img" jdbcType="BLOB"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
<select id="selectById" resultMap="userMap">
select * from t_user where id = #{id}
</select>
</mapper>
之前我们创建接口和XML文件时说过,接口和XML是通过将namespace的值设置为接口的全限定名称来进行关联的,那么接口中的方法和XML又是怎么关联的呢?通过观察上述代码,我们发现,XML文件中的select标签里的id属性值和接口类中的方法名是一样的。MyBatis正是通过这种方式将接口方法和XML中的SQL语句关联到一起的,如果接口方法没有和XML中的id属性值相对应,启动程序的话就会报错。接口中定义的返回值的类型必须和XML中配置的resultType类型一致,否则会因为类型不一致抛出异常。返回值的类型是由XML中的resultType(或resultMap中的type)来决定的。UserMapper接口中定义的selectById()方法,通过Id值来查找用户,最多只有一条数据,所以我们定义的返回值类型是User的类类型。
接下来,我们便可以编写测试类进行测试了。为了后续测试方便,提高代码的复用,我们先写一个基本的测试类,然后让其他的测试文件去继承这个基本测试类。在test目录下,新建一个TestMapper的包,来存放各种测试文件。
//BaseTest.java
package TestMapper;
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.junit.BeforeClass;
import java.io.IOException;
import java.io.Reader;
public class BaseMapperTest {
private static SqlSessionFactory sqlSessionFactory;
@BeforeClass
public static void init() {
try {
String resource = "Mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
上述代码具体说明如下:
- 通过Resurces工具类将Mybatis-config.xml配置文件读入Reader
- 再通过SqlSessionFactoryBuilder建造类使用Reader创建SqlSessionFactory工厂对象。在创建SqlSessionFactory对象的过程中,首先解析Mybatis-config.xml配置文件,读取配置文件中的mappers配置后会读取全部的xxxMapper.xml进行具体方法的解析,在这些解析完成后,SqlSessionFactory就包含了所有的属性配置和执行SQL的信息
- 使用时,通过SqlSessionFactory工厂对象获取一个SqlSession
然后我们就可以写刚才的selectById()方法的测试类了,命名为UserMapperTest.java,存放在TestMapper下:
package TestMapper;
import Interface.UserMapper;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import pojo.User;
import java.util.*;
public class UserMapperTest extends BaseMapperTest {
@Test
public void testSelectById() {
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(1001L);
System.out.println(user);
} finally {
sqlSession.close();
}
}
}
UserMapperTest继承自BaseTest,结合刚才分析过的基础测试文件,现在对UserMapperTest.java进行分析:
- 使用时,通过调用getSqlSession()方法获取一个SqlSession对象
- 通过SqlSession的getMapper()方法找到UserMapper,即通过UserMapper接口的class对象来获取接口方法,然后再通过UserMapper来执行selectById()方法,这一步就相当于执行SQL语句
- 之后将查询结果输出即可
- 最后一定不要忘记关闭sqlSession,不然会因为连接没有关闭而导致数据库连接数过多,造成系统崩溃
执行此测试文件,输出结果如下:
和我们插入的数据完全一样。此时,第一次查询成功进行。接下来我们考虑一下如何进行全部数据的查询与输出。首先我们需要明白的是:按照用户Id查询的时候最多只有一条数据,所以我们的方法返回值类型是User类类型,可以直接使用println()打印输出。但是当我们查询全部数据的时候,查询结果往往不止一条,那么我们就需要使用List<>来存储查询结果,然后循环输出。操作过程和selectById()方法的操作过程一样,先在UserMapper接口类文件中写selectAll()方法,然后在UserMapper.xml文件中写SQL查询代码:
package Interface;
import pojo.User;
import java.util.List;
public interface UserMapper {
//查询所有用户
List<User> selectAll();
}
<?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="Interface.UserMapper">
<!--查找所有用户-->
<select id="selectAll" resultType="pojo.User">
select id,user_name userName,user_password userPassword,
user_email userEmail,user_info userInfo,head_Img headImg,create_time createTime
from t_user
</select>
</mapper>
其实仔细观察一下UserMapper.xml文件中的selectById()方法和selectAll()方法的代码书写就会发现:selectById()中使用了resultMap来设置结果映射,而selectAll()中通过resultType来直接指定了返回结果的类型。也由此可以发现,如果使用resultType来设置返回结果的类型的时候,需要在SQL中为所有列名和属性名不一致的列设置别名,通过设置别名可以使最终的查询结果列和resultType指定对象的属性名保持一致,进而实现自动映射。
然后我们写selectAll()方法的测试类,其大致和selectById()方法一样。还是写在UserMapperTest.java文件中:
package TestMapper;
import Interface.UserMapper;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import pojo.User;
import java.util.*;
public class UserMapperTest extends BaseMapperTest {
@Test
public void testSelectAll() {
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAll();
for ( User user : userList) {
System.out.println(user);
}
} finally {
sqlSession.close();
}
}
}
请注意结果的输出方法。执行该测试方法,得到如下结果:
之前我们说过,当使用resultType来指定返回结果的类型的时候,需要注意在SQL中为所有列名和属性名不一致的列设置别名,通过设置的别名来使最终的查询结果列和resultType指定对象的属性名保持一致。(个人理解为需要将数据库中表的各个字段与实体类中的各个属性一一对应)当然,如果我们不想这样设置别名的话也可以通过其他方法达到目标。第一种方法就是之前我们说过的在设计数据库表和在实体类中写实体属性的时候,将两者写成一样的,就避免了这种麻烦。还有一种方法就是在Mybatis-config.xml配置文件中增加一行配置内容:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
添加之后的新的Mybatis-config.xml文件为:
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/RoleMapper.xml"/>
<mapper resource="mapper/PrivilegeMapper.xml"/>
<mapper resource="mapper/UserRoleMapper.xml"/>
<mapper resource="mapper/RolePrivilegeMapper.xml"/>
</mappers>
</configuration>
这样设置的原因在于:在数据库中,由于大多数数据库设置不区分大小写,因此下划线方式的命名很常见。但是在Java中,一般都是用驼峰式命名,因为数据库和Java中的这两种命名方式很常见,因此MyBatis提供了一个全局的这样的一个属性,可以自动将以下划线方式命名的数据库列映射到Java对象的驼峰式命名属性中。我们写一个selectAll2()方法才测试一下。(selectAll2()命名方法并不规范,在此只为测试使用,无实际意义)
UserMapper接口类中的方法为:
package Interface;
import pojo.User;
import java.util.List;
public interface UserMapper {
//查询所有用户
List<User> selectAll2();
}
UserMapper.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="Interface.UserMapper">
<!--查找所有用户2-->
<select id="selectAll2" resultType="pojo.User">
select id,user_name,user_password,user_email,user_info,head_Img,create_time
from t_user
</select>
</mapper>
测试文件中的该方法测试代码为:
@Test
public void testSelectAll2() {
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAll2();
for ( User user : userList) {
System.out.println(user);
}
} finally {
sqlSession.close();
}
}
我们将两种selectAll()方法一起运行,看一下输出结果是否一样:
查询结果一样,selectAll2()与selectAll()方法相比只不过就是UserMapper.xml文件中的SQL语句少写了Java中的属性而已。
接下来我们看一下多表联合查询的操作方法和输出方法。其实SQL代码并不难写,难的是要想好如何输出查询结果。我们假设要查询用户id为1001的张三的角色都有哪些。根据我们之前插入的数据,我们知道,张三不仅是管理员,也是普通用户,所以输出结果肯定是一个List类型的。而且此次查询关联到的表有3个,即t_user用户表、t_role角色表和t_user_role用户角色联系表。还是和之前的处理方法一样,先写接口方法,再写查询的SQL语句,之后再编写测试方法。和之前不同的是,我们需要一些继承和属性的添加。
先在UserMapper接口中写方法,其实我们也可以在RoleMapper接口文件中写:
package Interface;
import pojo.User;
import pojo.Role;
import java.util.List;
public interface UserMapper {
//根据用户ID查询用户角色
List<Role> selectRoleByUserId(Long UserId);
}
如果只考虑输出Role的内容,那么我们就不能很快捷的知道用户名,所以我们必须得输出一下用户名。那么针对这种情况,很明显我们的返回值类型List
在角色表中新增一个属性,为User对象类型,字段名为user。其实此处还需要更改Role实体中的get()和set()方法,以及toString()方法。
private Long id; //角色ID
private String roleName; //角色名
private int enabled; //有效标志
private Long createAuthor; //创建者
private Date createTime; //创建时间
private User user;
然后,角色实体类Role中的属性和方法就变成了这样:
package pojo;
import java.util.Date;
public class Role {
private Long id; //角色ID
private String roleName; //角色名
private int enabled; //有效标志
private Long createAuthor; //创建者
private Date createTime; //创建时间
private User user;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public int getEnabled() {
return enabled;
}
public void setEnabled(int enabled) {
this.enabled = enabled;
}
public Long getCreateAuthor() {
return createAuthor;
}
public void setCreateAuthor(Long createAuthor) {
this.createAuthor = createAuthor;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", roleName='" + roleName + '\'' +
", enabled=" + enabled +
", createAuthor=" + createAuthor +
", createTime=" + createTime +
", user=" + user +
'}';
}
}
然后,在UserMapper.xml文件中写查询的SQL代码。注意,我们把方法写在了UserMapper接口类中,所以就尽量让XML文件和接口文件对应也写在UserMapper.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="Interface.UserMapper">
<!--根据用户ID查找用户角色-->
<select id="selectRoleByUserId" resultType="pojo.Role">
select r.id,r.role_name roleName,r.enabled,
r.create_author createAuthor,r.create_time createTime,
u.user_name as "user.userName",
u.user_email as "user.userEmail"
from t_user u
inner join t_user_role ur on u.id = ur.user_id
inner join t_role r on ur.role_id = r.id
where u.id = #{userId}
</select>
</mapper>
这里我们在设置列的别名的时候使用的是”user.属性名”,user就是Role实体中增加的User类型的对象。我们通过这种方式可以直接将值赋给user字段中的属性。然后在UserMapperTest.java中编写该方法的测试方法:
@Test
public void testSelectRoleByUserId() {
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<Role> roleList = userMapper.selectRoleByUserId(1001L);
for (Role role : roleList) {
System.out.println(role);
}
} finally {
sqlSession.close();
}
}
之后,调试运行即可:
运行结果和我们插入的数据一样,即张三既是管理员,也是普通员工。
Insert
有了此前我们select的经验后,insert就显得特别简单了。还是和之前一样,我们依然在User上进行插入操作。先从插入一个用户开始。方法和之前一样,先在UserMapper接口类中定义一个方法。
package Interface;
import pojo.User;
public interface UserMapper {
//插入一个用户
int addUser(User user);
}
然后在UserMapper.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="Interface.UserMapper">
<!--新增一个用户-->
<insert id="addUser">
insert into t_user(id,user_name,user_password,user_email,user_info,head_img,create_time)
value (#{id},#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
</insert>
</mapper>
然后我们就看可以编写测试方法进行测试了
@Test
public void testAddUser() {
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUserName("王五");
user.setUserPassword("789000");
user.setUserEmail("000000@study");
user.setUserInfo("我是王五");
user.setHeadImg(new byte[]{1,2,3});
user.setCreateTime(new Date());
int res = userMapper.addUser(user);
if(res > 0) {
System.out.println(res);
System.out.println("插入成功!");
}
} finally {
sqlSession.commit();
sqlSession.close();
}
}
我们插入了一个叫王五的,id为1003的用户。执行测试之后:
该用户插入成功。我们设置的res可以把它看作一个插入是否成功的标记,addUser()方法返回的是插入数据的条数,所以我们看到最后res返回了1。除此之外还需要注意最后的sqlSession.commit()方法,代表提交事务,我们进行了insert的操作之后,需要将该事务提交,否则虽然只是显示插入成功,但是数据库表中却找不到这条数据。如果我们怕影响其他操作,也可使用sqlSession.rollback()方法,来使该事务回滚,不会影响到其他操作。
除了像上述方法那样通过打印出的受影响的行数来判断插入是否成功之外还可以通过返回插入数据的主键来判断插入成功。我们写一个addUser2()方法,来打印插入数据的主键。
package Interface;
import pojo.User;
public interface UserMapper {
//插入一个用户
int addUser2(User user);
}
<?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="Interface.UserMapper">
<!--新增一个用户-->
<insert id="addUser2" useGeneratedKeys="true" keyProperty="id">
insert into t_user(id,user_name,user_password,user_email,user_info,head_img,create_time)
value (#{id},#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
</insert>
</mapper>
我们发现,addUser2()与addUser()相比,其实就只是在insert的标签上配置了两个属性而已。将useGeneratedKeys设置为true之后,MyBatis会使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键。获得主键值后将其赋值给keyProperty配置的id属性。
然后我们编写测试类进行测试:
@Test
public void testAddUser2() {
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUserName("李明");
user.setUserPassword("789000");
user.setUserEmail("000000@study");
user.setUserInfo("我是李明");
user.setHeadImg(new byte[]{1,2,3});
user.setCreateTime(new Date());
int res = userMapper.addUser2(user);
System.out.println(user.getId());
} finally {
sqlSession.commit();
sqlSession.close();
}
}
因为我们之前插入的王五使用了1003的id号,所以此时李明的id号为1004。
除了使用JDBC的方式返回主键之外,我们还可以使用selectKey来返回主键的值。编写addUser3()方法。
package Interface;
import pojo.User;
public interface UserMapper {
//插入一个用户
int addUser3(User user);
}
<?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="Interface.UserMapper">
<!--新增一个用户-->
<insert id="addUser3">
insert into t_user(id,user_name,user_password,user_email,user_info,head_img,create_time)
value (#{id},#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
<selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
</insert>
</mapper>
select标签的keyColumn、keyProperty和useGeneratedKeys的用法含义相同,resultType用于设置返回值类型。order属性的设置和使用的数据库有关,在MySQL中,order属性设置为AFTER,因为当前记录的主键值是在insert语句执行成功之后才获取到的,而在Oracle中,order要设置为BEFORE,因为Oracle中需要先从序列获取值,然后将值作为主键插入到数据库中。
@Test
public void testAddUser3() {
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUserName("李大明");
user.setUserPassword("789000");
user.setUserEmail("000000@study");
user.setUserInfo("我是李大明");
user.setHeadImg(new byte[]{1,2,3});
user.setCreateTime(new Date());
int res = userMapper.addUser3(user);
System.out.println(user.getId());
} finally {
sqlSession.commit();
sqlSession.close();
}
}
此时,插入的李大明的id号为1005。
update
update的更新操作相比插入和查询就简单的多了。常见的就是根据某个条件来更新其他数据。例如,现在我们要更新1004号李明的密码、邮箱等信息。操作方法和以前一样,分三步走。写方法→写SQL→测试。
package Interface;
import pojo.User;
public interface UserMapper {
//根据用户ID更新数据
int updateById(User user);
}
<?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="Interface.UserMapper">
<!--根据用户ID更新数据-->
<update id="updateById">
update t_user
set user_password = #{userPassword},user_email = #{userEmail},
head_Img = #{headImg,jdbcType = BLOB},create_time = #{createTime,jdbcType = TIMESTAMP}
where id = #{id}
</update>
</mapper>
@Test
public void testUpdateById() {
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(1004L);
user.setUserPassword("999999");
user.setUserEmail("999999@study");
user.setHeadImg(new byte[]{9,9,9});
user.setCreateTime(new Date());
int res = userMapper.updateById(user);
System.out.println("更新成功" + res + "条信息!");
} finally {
sqlSession.commit();
sqlSession.close();
}
}
更新数据成功!需要注意的是,执行update操作之后也需要提交。不然不会生效。
delete
delete操作和update操作一样简单,都只是需要一句SQL就能完成,现在我们删除刚才插入的1005号李大明的信息。
package Interface;
import pojo.User;
public interface UserMapper {
//根据ID删除用户
int deleteById(Long 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="Interface.UserMapper">
<!--根据ID删除用户-->
<delete id="deleteById">
delete from t_user where id = #{id}
</delete>
</mapper>
@Test
public void testDeleteById() {
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int res = userMapper.deleteById(1005L);
System.out.println(res);
System.out.println("删除成功!");
} finally {
sqlSession.commit();
sqlSession.close();
}
}
1005号用户删除成功!
至此我们对于简单的增删改查工作就到此为止吧,后续我们会进行一些动态的SQL查询。
还没有评论,来说两句吧...