在实现以下内容之前,得把环境搭建好Mybatis环境搭建
一、添加操作
在 UserDao 接口中新增 saveUser() 方法
public interface UserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
/**
* 保存方法
* @param user
*/
void saveUser(User user);
}
在 Dao映射文件 UserDao.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="com.Dao.UserDao">
<!-- 配置查询所有 -->
<select id="findAll" resultType="com.domain.User">
select * from user;
</select>
<!-- 保存用户-->
<insert id="saveUser" parameterType="com.domain.User">
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
</insert>
</mapper>
细节
:
parameterType 属性:
代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
sql 语句中使用#{}字符:
它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。
具体的数据是由#{}里面的内容决定的。
#{}中内容的写法:
由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。必须和User类的每个属性名一一对应,不可以乱写
属性名是实体类的getXxx()/setXxx()中Xxx
部份,大多数情况下就是成员变量名,也有少数情况不是成员变量名,也就是说成员变量和属性不能等同。
它用的是 ognl 表达式。
ognl 表达式:
它是 apache 提供的一种表达式语言,全称是:
Object Graphic Navigation Language 对象图导航语言
它是按照一定的语法格式来获取数据的。
语法格式就是使用 #{对象.对象}的方式
测试添加操作
package com.test;
import com.Dao.UserDao;
import com.domain.User;
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.InputStream;
import java.util.Date;
import java.util.List;
/**
* @author zhang
*/
public class MyBatisTest {
private InputStream in;
private SqlSession sqlSession;
private UserDao userDao;
/**
* 测试之前执行,用于初始化
*/
@Before
public void init() throws Exception {
//1. 读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3. 获取SqlSession对象
sqlSession = factory.openSession();
//4. 使用SqlSession创建Dao的代理对象
userDao = sqlSession.getMapper(UserDao.class);
}
/**
* 测试结束执行,用于提交事务和释放资源
*/
@After
public void destroy() throws Exception{
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
in.close();
}
/**
* 测试查询所有
*/
@Test
public void testFindAll(){
List<User> users = userDao.findAll();
for (User user : users){
System.out.println(user);
}
}
/**
* 测试保存操作
*/
@Test
public void testSave(){
User user = new User();
user.setUsername("一个Java小白");
user.setAddress("广东");
user.setSex("男");
user.setBirthday(new Date());
//执行保存方法
userDao.saveUser(user);
}
}
注意:
如果在destory()
方法中没有通过sqlSession.commit();
来提交事务的话,那么添加的记录不会写入到数据库中。
因为我们在调用openSession()
时并没有设置事务自动提交,所以最终事务会自动回滚,导致记录没有写入到数据库中。见下图
测试结果
执行查找方法发现成功增加了信息:
这里有人可能有疑问了,id49哪里去了呢,为什么会直接跳到50了呢。这是因为之前没有通过
sqlSession.commit();
来提交事务,它自动回滚,所以这也算占了一个位置。
- 在添加用户的时候,如果我们想获取新增用户的 id 值,那么就可以使用 标签,见代码:
<!-- 添加用户,同时获取 id 的返回值 -->
<insert id="saveUser" parameterType="cn.ykf.pojo.User">
<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>
keyProperty
表示 selectKey 语句结果应该被设置的目标属性(对应实体类)。
keyColumn
表示匹配属性的返回结果集中的列名称(对应数据库结果集)。
order
可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后再执行 selectKey 中的语句。
- 测试代码及运行结果:
/**
* 测试增加用户操作
*/
@Test
public void testSave(){
User user = new User();
user.setUsername("一个Java小白");
user.setAddress("广东");
user.setSex("男");
user.setBirthday(new Date());
System.out.println("添加前 : " + user);
//执行保存方法
int count = userDao.saveUser(user);
System.out.println("增加条数:"+count);
System.out.println("添加后 : " + user);
}
二、删除操作
在 UserDao 接口中新增 deleteUser() 方法
/**
* 根据 id 删除用户
* @param userId
* @return
*/
int deleteUser(Integer userId);
在 Dao映射文件 UserDao.xml 中配置删除操作
<!-- 删除用户 -->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{uid}
</delete>
这里有两点需要注意一下!
第一点,如果参数是基本类型或者基本类型的包装类,且只有一个参数,那么参数符号可以随便写 。也就是说,虽然 Dao接口中的方法声明为int deleteUser(Integer userId)
,但是映射文件中既可以写#{userId},也可以写 #{aaaa}。
(意思就是随便你写什么都可以)
第二点,parameterType
写int、INT、INTEGER、integer、java.lang.Integer
都可以。原因在后面的typeAliases 标签
会解释
测试删除操作
/**
* 测试删除操作
*/
@Test
public void testDelete(){
//执行删除方法
int count = userDao.deleteUser(58);
System.out.println("删除条数:"+count);
}
测试结果
三、修改操作
在 UserDao 接口中新增 updateUser() 方法
/**
* 修改用户
* @param user
*/
int updateUser(User user);
在 Dao映射文件 UserDao.xml 中配置修改操作
<!-- 修改用户 -->
<update id="updateUser" parameterType="com.domain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}
</update>
测试修改操作
/**
* 测试修改操作
*/
@Test
public void testUpdate(){
User user = new User();
// id 为自己数据库中存在的值
user.setId(50);
user.setUsername("一个Java小白(改)");
user.setAddress("广东");
user.setSex("女");
user.setBirthday(new Date());
//执行修改方法
int count = userDao.saveUser(user);
System.out.println("修改条数:"+count);
}
测试结果
四、查询操作
根据ID查询
在 UserDao 中添加方法
/**
* 根据ID查询用户信息
* @param userId
* @return
*/
User findById(Integer userId);
修改映射文件
<!-- 根据ID查询用户 -->
<select id="findById" parameterType="java.lang.Integer" resultType="com.domain.User">
select * from user where id = #{uid}
</select>
测试代码及结果
/**
* 测试查询操作
*/
@Test
public void testFindOne(){
//执行查询一个方法
User user = userDao.findById(42);
System.out.println(user);
}
模糊查询
在 UserDao 中添加方法
/**
* 根据名称模糊查询用户信息
* @param username
* @return
*/
List<User> findByName(String username);
修改映射文件
<!-- 根据名称模糊查询 -->
<select id="findByName" parameterType="java.lang.String" resultType="com.domain.User">
select * from user where username like #{username}
</select>
虽然
UserDao
中方法的返回值为List<User>
,但是映射文件中resultType
写User
就行。因为如果有多条记录的话,Mybatis 会自动帮我们封装成一个 List 集合。
这里的parameterType
也可以直接写String
。
参数符号可以随便写。
测试代码及结果
/**
* 测试模糊查询操作
*/
@Test
public void testFindByName(){
//执行模糊查询方法
List<User> users = userDao.findByName("%王%");
for (User user : users){
System.out.println(user);
}
}
这样所有包含“王”字的都被查询出来了。
我们在配置文件中没有加入%
来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%
。配置文件中的#{username}也只是一个占位符,所以 SQL 语句显示为“?”。
模糊查询的另一种配置方式
- 第一步:修改 SQL 语句的配置,配置如下:
<!-- 根据名称模糊查询 --> <select id="findByName" parameterType="string" resultType="com.domain.User">
select * from user where username like '%${value}%'
</select>
我们在上面将原来的#{}占位符,改成了
$ {value}
。注意如果用模糊查询的这种写法,那么${value}的写法就是固定的,不能写成其它名字
。
- 第二步:测试,如下:
/**
* 测试模糊查询操作
*/
@Test
public void testFindByName(){
//5.执行查询一个方法
List<User> users = userDao.findByName("王");
for(User user : users){
System.out.println(user);
}
}
可以发现,我们在程序代码中就不需要加入模糊查询的匹配符
%
了,这两种方式的实现效果是一样的,但执行的语句是不一样的
。
那这两种方式有什么区别呢?用哪种比较好呢?
第一种方式,采用的是Statement对象的字符串拼接SQL。
第二种方式,采用的是PrePatedStatement的参数占位符。
在实际开发中,我们一般使用的是第一种方式。
拓展一下:
#{}与${}的区别:
#{}
表示一个占位符号 通过#{}
可以实现preparedStatement
向占位符中设置值,自动进行java
类型和jdbc
类型转换,#{}
可以有效防止 sql 注入。#{}
可以接收简单类型值或pojo
属性值。 如果parameterType
传输单个简单类 型值,#{}
括号中可以是value 或其它名称
。
$ {}
表示拼接sql
串 通过$ {}
可以将parameterType
传入的内容拼接在sql
中且不进行jdbc
类型转换,$ {}
可以接收简 单类型值或pojo
属性值,如果parameterType
传输单个简单类型值,${}
括号中只能是value
。
使用聚合函数查询用户总数
在 UserDao 中添加方法
/**
* 查询总用户条数
* @return
*/
int findTotal();
修改映射文件
<!-- 查询用户的总记录条数 -->
<select id="findTotal" resultType="int">
select count(id) from user
</select>
测试代码及运行结果
/**
* 测试总记录条数
*/
@Test
public void testFindTotal(){
//执行查询一个方法
int count = userDao.findTotal();
System.out.println("总记录条数:" +count);
}
五、拓展操作
使用 pojo 包装类进行查询
我们在上面中已经介绍了
SQL
语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型
,引用类型(例如:String 类型)
,还可以是实体类类型(POJO 类)
。同时也可以使用实体类的包装类
,现在我们来看看如何使用实体类的包装类作为参数传递。
注意:
基 本 类 型
和String
我 们 可 以直 接
写 类 型 名 称 , 也 可 以 使 用包 名 . 类 名
的 方 式 , 例 如 :java.lang.String
。实体类类型
,目前我们只能使用全限定类名
。- 究其原因,是 mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在最后的时候我们将会了解如何注册实体类的别名。
首先介绍一下 OGNL 表达式:
- 全称 Object Graphic Navigation Language ,即对象图导航语言
- 它是通过对象的取值方法来获取数据。在写法上把get给省略了。
比如:我们获取用户的名称:
类中的写法:user.getUsername();
OGNL的写法:user.username(); - MyBatis中为什么能直接写username,而不用user呢?
因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名。
传递 pojo 包装对象
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
Pojo 类中包含 pojo。
需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
编写 QueryVo 类来封装查询条件
package com.domain;
/**
* 用于封装查询条件
*
*/
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
编写接口层代码和映射文件
/**
* 根据 QueryVo 中的条件查询用户
* @param vo
* @return
*/
List<User> findUsersByVo(QueryVo vo);
<!-- 根据用户名称模糊查询,参数变成一个 QueryVo 对象了 -->
<select id="findUsersByVo" parameterType="com.domain.QueryVo" resultType="com.domain.User">
select * from user where username like #{user.username}
</select>
</mapper>
测试代码及运行结果
/**
* 测试使用QueryVo作为查询条件
*/
@Test
public void testFindByVo(){
QueryVo vo = new QueryVo();
User user = new User();
user.setUsername("%王%");
vo.setUser(user);
//执行模糊查询方法
List<User> users = userDao.findUsersByVo(vo);
for (User u : users){
System.out.println(user);
}
}
使用 resultMap 接收查询结果
resultType
属性可以指定结果集的类型,它支持基本类型和实体类类型。我们在前面已经对此属性进行过应用了。- 需要注意的是,它和
parameterType
一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名- 同时,当是实体类名称是,还有一个要求,实体类中的
属性名称必须和查询语句中的列名保持一致
,否则无法 实现封装。
但是在某些情况下,实体类的属性和数据库表的列名并不一致,那么就会出现查询结果无法封装进实体类。修改实体类进行演示:
package com.domain;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.XYChart;
import java.io.Serializable;
import java.util.Date;
/**
* @author zhang
*/
public class User implements Serializable {
// 此时实体类属性与数据库表的列表已经不一致了
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
// 此处不展示 getter()/setter()...和toString()...
//使用快捷键Alt+Ins即可
}
修改映射文件中的参数符号,并测试原本的查询方法 testFindAll() ,运行结果如下
原本应该所有的属性都为 null ,但是为什么名称会有值呢?由于在 Windows 环境下 MySQL 不区分大小,所以
userName
等同username
,不过要注意在 Linux 环境下 MySQL 严格区别大小写
。
为了解决实体类属性名与数据库表列名不一致,有以下解决方法:
- 在 SQL 语句中为所有列起别名,使别名与实体类属性名一致(执行效率相对高,开发效率低)
<!-- 配置查询所有 -->
<select id="findAll" resultType="com.domain.User">
select id as userId,username as userName,address as userAddress,sex as userSex,birthday as userBirthday from user;
</select>
- 使用 resultMap 完成结果集到实体类的映射(执行效率相对低,开发效率高)
<mapper namespace="com.Dao.UserDao">
<!-- 配置查询结果的列名和实体类的属性名的对应关系
type 属性:指定实体类的全限定类名
id 属性:给定一个唯一标识,是给查询 select 标签引用用的。-->
<resultMap id="userMap" type="com.domain.User">
<!-- 主键字段的对应 -->
<id property="userId" column="id"></id>
<!-- 非主键字段的对应 -->
<result property="userName" column="username"></result>
<result property="userBirthday" column="birthday"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
</resultMap>
<!-- 配置查询所有 -->
<select id="findAll" resultMap="userMap">
<!--select id as userId,username as userName,address as userAddress,sex as userSex,birthday as userBirthday from user;-->
select * from user;
</select>
- id 标签:用于指定主键字段
- result 标签:用于指定非主键字段
- column 属性:用于指定数据库列名
- property 属性:用于指定实体类属性名称
六、编写Dao实现类和代理Dao的执行过程分析
这两种方式的执行过程,大家可以看看下面两张图:
编写Dao实现类的执行过程分析:
代理Dao的执行过程分析:
至于里面怎么那么多没见过的接口和类还有方法呢,大家可以自行的debug打断点尝试,然后进入其中涉及到的类或者接口当中。自定义Dao实现类的代码我就不放出来了,有兴趣的可以自行去编写。
怎么进入想进入的方法比如(代理Dao中的getMapper()方法)呢?Ctrl+左键
那怎么找这些方法的实现类呢?如图:
至于选哪个呢?就得打个断点来看了。知道之后就可以进入了
然后就以此类推,在里面找需要找的方法,继续打断点,直到代码执行结束
七、Mybatis 配置文件标签讲解
properties 标签
在配置数据库连接的时候,我们可以采用以下几种方式来配置:
- 第一种,采用全局的内部配置。采用这种方式的话,如果需要配置多个数据库环境,那么像
username
、password
等属性就可以复用,提高开发效率。
<?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>
<!-- 配置properties -->
<properties>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置MySQL的环境 -->
<environment id="mysql">
<!-- 配置事务类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置数据库的基本信息 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/dao/UserDao.xml"/>
</mappers>
</configuration>
- 第二种,使用
resources
属性引入外部配置文件(常用)
编写配置文件 jdbcConfig.properties。配置文件名没有限制,但是配置文件一定要放在类路径下
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456
上面的URL中不用& amp;而是用&就行了
修改 Mybatis 配置文件:
<properties resource="jdbcConfig.properties"/>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置MySQL的环境 -->
<environment id="mysql">
<!-- 配置事务类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置数据库的基本信息 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
- 第三种,使用 url 属性引入外部配置文件
该方法的外部文件可以放在任意位置,但是路径写法必须按照 Url 的方式,写起来比较麻烦,推荐第二种方法
<!-- 引入外部文件 -->
<properties url="file:///D:/document/IdeaProjects/java_web_ssm/my_mybatis/src/main/resources/jdbcConfig.properties"/>
- URL:Uniform Resouce LOcator,即统一资源定位符。它可以唯一标识一个资源的位置,由四部分组成:
协议、主机、端口、路径
。- 例如:http://localhost:8080/mybatisserver/demo1,其中
http
为协议,localhost
为主机,8080
为端口号,/mybatisserver/demo1
为uri(路径)- URI:
Uniform Resource Identifier
,即统一资源标识符。它是在应用中可以唯一定位一个资源的。
typeAliases 标签
之前在编写映射文件的时候,resultType
这个属性可以写int、INT
等,就是因为 Mybatis 给这些类型起了别名。Mybatis 内置的别名如表格所示:
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
Integer | Integer |
double | Doublle |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
如果我们也想给某个实体类指定别名的时候,就可以采用 typeAliases 标签。用法如下:
<configuration>
<!--配置别名-->
<typeAliases>
<typeAlias type="com.domain.User" alias="user"/>
</typeAliases>
<!-- 其他配置省略... -->
</configuration>
typeAlias
子标签用于配置别名。其中type
属性用于指定要配置的类的全限定类名(该类只能是某个domain实体类),alias
属性指定别名。一旦指定了别名,那么别名就不再区分大小写
。- 也就是说,此时我们可以在映射文件中这样写
resultType="user"
,也可以写resultType="USER"
。
当我们有多个实体类需要起别名的时候,那么我们就可以使用 package 标签。
<typeAliases>
<package name="com.domain"/>
</typeAliases>
package
标签指定要配置别名的包,当指定之后,该包下的所有实体类都会注册别名,并且别名就是类名,不再区分大小写
package 标签还可以将某个包内的映射器接口实现全部注册为映射器,如下所示
<!-- 指定映射文件 -->
<mappers>
<package name="com.Dao"/>
</mappers>
这样配置后,我们就无需一个一个地配置 dao 接口了。不过,这种配置方式的前提是映射配置文件位置必须和dao接口的包结构相同。