目录
1.mybatis的CRUD案例
环境:
数据库环境与第一篇博客中相同,数据库中原始数据如下:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.cqu</groupId>
<artifactId>MybatisDemo02</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
实体类User
package cn.cqu.entity;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
public User(){
}
public User(String username, String birthday, String sex, String address) {
this.username = username;
this.birthday = new Date(birthday);
this.sex = sex;
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
mybatis的主配置文件
<?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">
<!--mybatis的主配置文件-->
<configuration>
<!-- 配置好环境 -->
<!--此处default不管为什么,必须在environment中有定义-->
<environments default="mysql">
<!--配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件
如果是使用注解来配置的话,此处应该使用class属性指定被注解的mapper全限定类名-->
<mappers>
<!-- 基于XML方式的配置 -->
<mapper resource="cn/cqu/mapper/IUserMapper.xml"/>
<!-- 基于注解方式的配置 -->
<!-- <mapper class="cn.cqu.mapper.IUserMapper"/>-->
</mappers>
</configuration>
我们在开发中,实际上只需要配置接口中的方法和配置配置文件
IUserMapper.java
package cn.cqu.mapper;
import cn.cqu.entity.User;
import java.util.List;
/**
* 用户的持久层接口
*/
public interface IUserMapper {
/**
* 查询所有操作
*/
List<User> findAll();
/**
* 保存用户
*/
void saveUser(User user);
/**
* 更新用户
*/
void updateUser(User user);
/**
* 根据User的id删除用户
* @param userId
*/
void deleteUser(Integer userId);
/**
* 根据User的id查询用户信息
* @param userId
* @return
*/
User findByUserId(Integer userId);
/**
* 根据姓名进行模糊查询
* @param username
* @return
*/
List<User> findByUsername(String username);
/**
* 查询总用户数
* @return
*/
int findTotal();
}
IUserMapper.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="cn.cqu.mapper.IUserMapper">
<!--查询所有-->
<select id="findAll" resultType="cn.cqu.entity.User">
select * from user
</select>
<!--保存用户-->
<insert id="saveUser" parameterType="cn.cqu.entity.User">
<!--配置插入操作后,获取插入数据的id-->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into user(username,birthday,sex,address)values(#{username},#{birthday},#{sex},#{address})
</insert>
<!--更新用户-->
<update id="updateUser" parameterType="cn.cqu.entity.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>
<!--删除用户-->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from USER where id=#{uid};
<!--当只有一个参数且该参数是基本类型或基本类型的包装类的时候,这里的#{}中的只是一个占位符,写成任何字符串都可以-->
</delete>
<!--根据id查询用户-->
<select id="findByUserId" parameterType="java.lang.Integer" resultType="cn.cqu.entity.User">
select * from user where id=#{uid}
</select>
<!--根据姓名查询用户-->
<select id="findByUsername" parameterType="java.lang.String" resultType="cn.cqu.entity.User">
<!-- 以下这种写法,我们自己在传入参数的时候根据需要传入通配符,推荐使用这种方式,这种方式使用的是PreStatement的占位符-->
<!-- select * from user where username like #{username}-->
<!-- 这种方式使用的是Statement的字符串拼接-->
select * from user where username like "%${value}%"
</select>
<!--查询总用户数-->
<select id="findTotal" resultType="java.lang.Integer">
select count(*) from User
</select>
</mapper>
测试:
package cn.edu.cqu.test;
import cn.cqu.entity.User;
import cn.cqu.mapper.IUserMapper;
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.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* 单元测试每种方法
*/
public class MybatisTest {
private InputStream in;
private SqlSession session;
private IUserMapper userMapper;
@Before
public void init() throws IOException {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlFactorySession工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产一个SqlFactorySession对象
session = factory.openSession();
//4.使用SqlFactorySession创建Mapper接口的代理对象
userMapper = session.getMapper(IUserMapper.class);
}
@After
public void destroy() throws IOException {
//6.提交事务
session.commit();
//7.释放资源
session.close();
in.close();
}
@Test
public void testSelectAll() {
//5.使用代理对象执行方法
List<User> users = userMapper.findAll();
for(User user : users)
{
System.out.println(user);
}
}
@Test
public void testSave() {
User user = new User("mybatis saveUser","2019/10/5","男","重庆市沙坪坝区");
System.out.println(user);
userMapper.saveUser(user);
System.out.println(user);
}
@Test
public void testUpdate() {
User user = new User("mybatis updateUser","2019/10/5","男","重庆市沙坪坝区");
user.setId(45);
userMapper.updateUser(user);
}
@Test
public void testDelete() {
userMapper.deleteUser(48);
}
@Test
public void testFindByUserId() {
User user = userMapper.findByUserId(46);
System.out.println(user);
}
@Test
public void testFindByUsername() {
//List<User> users = userMapper.findByUsername("%李%");
List<User> users = userMapper.findByUsername("李");
for(User user : users)
{
System.out.println(user);
}
}
@Test
public void testFindTotal() {
int total = userMapper.findTotal();
System.out.println(total);
}
}
以上是完整代码,以下是流程演示
以下用户接口均指的是IUserMapper接口,用户的映射配置文件均指IUserMapper.xml
1.1 保存用户(插入)
(1)在用户接口中添加saveUser方法
(2)在用户的映射配置文件中配置
细节:
parameterType 属性:
- 代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
sql 语句中使用#{}字符:
- 它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。具体的数据是由#{}里面的内容决定的。
#{}中内容的写法:
- 由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。它用的是 ognl 表达式。
新增用户后,同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment 的值返回。
所以上述配置中配置了如下:
- order表示在插入sql语句执行后执行
(3) 添加测试类中的测试方法
@Test
public void testSave() {
User user = new User("mybatis saveUser","2019/10/5","男","重庆市沙坪坝区");
System.out.println("保存前:"+user);
userMapper.saveUser(user);
System.out.println("保存后:"+user);
}
运行结果:
注意:
- 这里已经将其他创建SqlSession等等的语句,抽象出了字段,并且将它们实现在了init和after方法中,而其他测试方法中只用写核心逻辑代码
- 更新和插入要执行成功,必须提交事务,所以以下最后有一步是提交事务session.commit()
1.2 更新用户
(1)在用户接口中添加saveUser方法
(2)在用户的映射配置文件中配置
(3)加入更新的测试方法
@Test
public void testUpdate() {
User user = new User("mybatis updateUser","2019/10/5","男","重庆市沙坪坝区");
user.setId(45);
userMapper.updateUser(user);
}
运行结果:
1.3 删除用户
(1)在用户接口中添加删除方法
(2)在用户的映射配置文件中配置
(3)加入删除的测试方法
@Test
public void testDelete() {
userMapper.deleteUser(48);
}
1.4 根据id查询
(1)在持久层接口中添加 findByUserId 方法
(2)在用户的映射配置文件中配置
(3)加入模糊查询的测试方法
@Test
public void testFindByUserId() {
User user = userMapper.findByUserId(46);
System.out.println(user);
}
1.5 用户模糊查询
(1)在用户接口中添加模糊查询方法
(2)在用户的映射配置文件中配置
(3)加入模糊查询的测试方法
@Test
public void testFindByUsername() {
//List<User> users = userMapper.findByUsername("%李%");
List<User> users = userMapper.findByUsername("李");
for(User user : users)
{
System.out.println(user);
}
}
(4)关于模糊查询的两种写法说明
- 推荐以下第一种写法
- 我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%。配置文件中的#{username}也只是一个占位符,所以 SQL 语句显示为“?”。
- 可以发现,我们在程序代码中就不需要加入模糊查询的匹配符%了,这两种方式的实现效果是一样的,但执行的语句是不一样的。
(5) #{}与${}的区别
- #{}表示一个占位符号
- 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换, #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类 型值,#{}括号中可以是 value 或其它名称。
- ${}表示拼接 sql 串
- 通过${}可以将 parameterType 传入的内容拼接在 sql中且不进行 jdbc 类型转换, ${}可以接收简 单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。
1.6 查询使用聚合函数
(1)在用户接口中添加查询记录总数的函数
(2)在用户的映射配置文件中配置
(3)加入模糊查询的测试方法
@Test
public void testFindTotal() {
int total = userMapper.findTotal();
System.out.println(total);
}
2.parameterType 配置参数
SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类
2.1 传递基本类型
- 基本类型和 String 我 们可以直接写类型名称 ,也可以使用包名 . 类名的方式 ,例如 : java.lang.String。
在 mybatis 的官方文档的说明(链接:https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases)
这些都是支持的默认别名。我们也可以从源码角度来看它们分别都是如何定义出来的。 可以参考 TypeAliasRegistery.class 的源码:
2.2 传递实体类型(POJO)
- 实体类类型,目前我们只能使用全限定类名。 究其原因,是 mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名, 而我们的是实体类并没有注册别名,所以必须写全限定类名。
- Mybatis使用ognl表达式来解析对象字段的值,#{}或者${}括号中的值为pojo属性名称
ognl 表达式:
- 它是 apache 提供的一种表达式语言,全称是: Object Graphic Navigation Language 对象图导航语言 它是按照一定的语法格式来获取数据的。
- 它是通过对象的取值方法来获取数据,在写法上把get给省略了
- 比如:我们获取用户的名称,正常时user.getUsername(),而ognl表达式的写法是user.username
- 语法格式就是使用 #{对象.对象}的方式
- #{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用 getUsername()方法把值取出来。
- mybatis中为什么能直接写username,而不用user呢?
- 因为在 parameterType 中已经提供了属性所属的类,所以可以省略 user. 而直接写 username。
- 当然如果在使用ognl表达式时,没有提供属性所属的类,我们就需要写
2.3 传递POJO包装对象
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。 Pojo 类中包含 pojo。
演示:
定义一个条件类
package cn.cqu.entity;
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
我们的需求是:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
在用户接口中添加
在用户的映射配置文件中配置
加入模糊查询的测试方法
@Test
public void testFindUserByVo() {
QueryVo queryVo = new QueryVo();
User user01 = new User();
user01.setUsername("%李%");
queryVo.setUser(user01);
List<User> users = userMapper.findUserByVo(queryVo);
for(User user : users)
{
System.out.println(user);
}
}
3.Mybatis 的输出结果封装
3.1 resultType 配置结果类型
- resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。
- 需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名、 同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装
上述案例中我们已经使用过此参数,此处不再赘述
我们上面的案例中是将实体类User的字段与数据库中的一致,如果不一致呢?我们接下来对User修改如下(与数据库中字段名称不一致):
package cn.cqu.entity;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
public User(){
}
public User(String userName, String userBirthday, String userSex, String userAddress) {
this.userName = userName;
this.userBirthday = new Date(userBirthday);
this.userSex = userSex;
this.userAddress = userAddress;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}
public String getUserSex() {
return userSex;
}
public void setUserSex(String userSex) {
this.userSex = userSex;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userBirthday=" + userBirthday +
", userSex='" + userSex + '\'' +
", userAddress='" + userAddress + '\'' +
'}';
}
}
Dao 接口
映射配置
测试查询结果
- 可以发现由于数据库的字段和实体类的字段名称不一致,所以并没有封装进去
- 但为什么名称会有值呢?
- 因为:mysql 在 windows 系统中不区分大小写! 在Linux下是严格区分的
第一种改进方式:使用别名查询
- 这种方式由于是修改sql语句本身,所以执行效率高
第二种方式:使用resultMap
3.2 resultMap 结果类型
- resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。
- 在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询
在配置文件中配置resultMap
- id 标签:用于指定主键字段
- result 标签:用于指定非主键字段
- column 属性:用于指定数据库列名
- property 属性:用于指定实体类属性名称
- 由于这种配置要解析配置文件,所以没有第一种方式执行效率高