Mybatis
Mybatis介绍
-
Mybatis是一款优秀的持久层框架,是用于简化JDBC开发
- 持久层就是负责将数据保存到数据库的那一层代码
实际上,之前文章关于JDBC的学习(juejin.cn/post/743636…),其代码书写,我们已经能够一定程度上感受到其缺陷:
public static void main(String[] args) throws Exception{
//获取驱动、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//连接数据库
String url ="jdbc:mysql://127.0.0.1:3306/learnjdbc";
String username = "root";
String psw = "nylonmin747599";
//建立连接
Connection conn = DriverManager.getConnection(url, username, psw);
String sql = "select * from students where name like ? and score >?";
//为上面的sql语句,创建PreparedStatement对象
PreparedStatement preparedStatement = conn.prepareStatement(sql);
//分别测试sql语句中问号占位符的值
preparedStatement.setString(1, "小%");
preparedStatement.setInt(2, 80);
//执行查询语句,前面实例化PreparedStatement的时候已经把sql放进去了,这里就不用
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
System.out.println(resultSet.getString("name"));
}
preparedStatement.close();
conn.close();
}
-
硬编码 : 所谓硬编码就是把某些关键信息直接作为字符串写入到代码中,在之后如果需要修改什么的,那就会很麻烦。
- MyBatis对于这种东西的解决方案就是,使用配置文件,把关键信息都放入到配置文件中。
- 比如注册驱动,获取连接
- SQL语句
-
操作繁琐 ,比如使用prepareStatement需要一个个来设置参数并且获取结果也只能一个个获取。Mybatis直接自动完成
- 繁琐在于: 手动设置参数
- 繁琐在于:手动封装结果集
Mybatis快速入门
解决IDEA连接数据库后,xml中写SQL语句不提醒数据库表字段问题
这里实际上也要转换思维,并不要认为Mybatis是什么高大上的东西,它只不过是一个框架,对JDBC原始操作进行了封装。从结果上来看,Mybatis框架进行的操作与JDBC并没有任何不同。
这里借鉴黑马程序员教学的示例,以此来说Mybatis一般且原始的操作。
-
创建数据库表,比方说user表,添加数据
-
创建模块,导入坐标
-
编写MyBatis核心配置文件 --> 替换连接信息,解决硬编码问题
-
编写SQL映射文件 ——>统一管理sql语句,解决硬编码问题
-
编码
- 定义POJO类
- 加载核心配置文件,获取
SqlSessionFactory对象 - 获取
SqlSessionFactory对象,执行SQL语句 - 释放资源
这里就一句上面的过程一步步来说明:
1、创建数据库表不必多说
2、创建模块,导入坐标。这里直接使用IDEA创建Maven项目,通过导入mybatis坐标的方式来达到导入jar包的目的。
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
3、编写Mybatis核心配置文件,放在resource下面
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 数据库连接信息 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="nylonmin747599"/>
</dataSource>
</environment>
</environments>
<!-- 指定sql映射文件所在位置 -->
<!-- 加载sql映射文件 -->
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
1、这里就是为了解决硬编码问题。避免了原生JDBC在连接数据库时,无论是获取mysql驱动还是通过驱动与数据库建立连接。
2、指定sql映射文件所在位置以及加载sql映射文件,都是为了简化后续sql语句的书写以及复用。
4、建立SQL映射文件,这里就指的是UserMapper.xml
下面这里namespace名称空间实际上就有点类似于是一个标识符,表示这一整个Mapper文件。
下面的<select> 标签里面的 id 实际上指代的是这一条sql语句的唯一标识
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace 名称空间
-->
<mapper namespace="user">
<select id="selectAllUser" resultType="com.itheima.pojo.User">
select * from tb_user
</select>
</mapper>
5、编码,定义pojo类
▲ 这里需要特别注意,定义的pojo类的成员变量需要与数据库表中的字段类型相同,并且成员变量名也要和数据库表中的字段名相同。
public class Demo {
public static void main(String[] args) throws Exception {
// 加载Mybatis核心配置文件,获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取sqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行sql
//具体执行哪个mapper文件下的哪个sql语句,实际上是要 namespace+id
List<User> userList = sqlSession.selectList("user.selectAllUser");
System.out.println(userList);
}
}
Mapper代理开发
使用Mapper代理开发的要求
-
定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录之下
-
设置SQL映射文件的namespace属性为Mapper接口全限定名
-
在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并且保持参数类型和返回值类型一致
-
编码:
- 通过SqlSession的getMapper方法获取Mapper接口的代理对象
- 调用对应方法完成sql的执行
下面就一步步地进行代码以及操作来一步步说明:
1、定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录之下
这里我们定义完了与SQL映射文件同名的Mapper接口 —— UserMapper
文件结构如下:
这时可以看到,尚未达到Mapper接口与SQL映射文件在同一目录之下。
如果我移动SQL映射文件,到Mapper文件夹之下,如图:
这里固然符合条件了,但是确从根本上不符合我们的变成要求,代码的接口又怎么能够和SQL映射文件这样子放呢?实际上,我们将整个项目进行编译(PS:通过IDEA安装Maven helper插件,然后右键整个项目->run maven-> compile )之后,就可以知道他们实际上,编译之后路径如下:
UserMapper.xml 编译后路径为:E:\java\code\MavenLearn\target\classes\UserMapper.xml
UserMapper接口编译后路径为:E:\java\code\MavenLearn\target\classes\com\itheima\Mapper\UserMapper.class
也就是说: 我们只需要在IDEA中,在项目文件下,新建一个路径,跟UserMapper接口同样即可,如下图所示:(这样子编译之后,就可以看到,编译后他俩就都在同一个路径下了)
那么此时同样mybatis-config.xml中的加载sql映射文件路径就需要改一下
<!-- 指定sql映射文件所在位置 -->
<!-- 加载sql映射文件 -->
<mappers>
<mapper resource="com/itheima/Mapper/UserMapper.xml"/>
</mappers>
2、设置SQL映射文件的namespace属性为Mapper接口全限定名
将UserMapper.xml内容编写如下,着重看namespace
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace 名称空间
-->
<mapper namespace="com.itheima.Mapper.UserMapper">
<select id="selectAllUser" resultType="com.itheima.pojo.User">
select * from tb_user
</select>
</mapper>
3、在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并且保持参数类型和返回值类型一致
实际上这里的返回值还是得稍微揣测一下那个select的sql语句的返回,像上面写的是select * ,那么显然就是返回多个数据
package com.itheima.Mapper;
import com.itheima.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> selectAllUser();
}
4、编码:
- 通过SqlSession的getMapper方法获取Mapper接口的代理对象
- 调用对应方法完成sql的执行
package com.itheima;
import com.itheima.Mapper.UserMapper;
import com.itheima.pojo.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 java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class Demo {
public static void main(String[] args) throws Exception {
// 加载Mybatis核心配置文件,获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取sqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行sql
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAllUser();
System.out.println(userList);
}
}
注:正是由于第一个条件,即Mapper.xml文件和Mapper接口都需要放到同一个目录下面,那么我们可以预见的是,之后再要出现新的Mapper.xml文件一定也是放在这个目录下。那么在mybatis-config.xml文件中就可以使用包扫描来进行,即:
<mappers>
<!-- <mapper resource="com/itheima/Mapper/UserMapper.xml"/>-->
<!-- 包扫描方式 -->
<package name="com.itheima.Mapper"/>
</mappers>
仅供个人参考,我大胆猜测,第一步至关重要,第一步完成了之后,结果就是编译后mapper.xml文件和接口文件就在一个路径下了。这时候,在运行时在进行反射,直接通过反射把sql语句反射到接口中然后进行调用实现。
查询语句
简单条件查询
这里的设置基本上跟前面的一样,配置核心文件、创建pojo实体类、创建xml文件、创建对应的Mapper接口。
我们现在重点关于Mapper.xml
比如BrandMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace 名称空间
-->
<mapper namespace="com.itheima.Mapper.BrandMapper">
<select id="selectAll" resultType="com.itheima.pojo.Brand">
select * from tb_brand;
</select>
<select id="selectById" resultType="com.itheima.pojo.Brand">
select * from tb_brand where id=#{id};
</select>
</mapper>
上面代码中,我们主要关注根据指定的id来查找对应的品牌
<select id="selectById" resultType="com.itheima.pojo.Brand">
select * from tb_brand where id=#{id};
</select>
0. 参数占位符
* `#{}` : 执行SQL时,会将 `#{}`占位符替换为`?`,将来自动设置参数值。这里有没有感受到 ,实际上就有点像我们前面学习JDBC时候使用到的`PrepareStatement` ,同理的,能够有效方式SQL注入问题。
* `${}`: 直接拼接SQL。 会存在sql注入的问题,一般很少使用
* 使用时机:
* 参数传递,都是用 `#{}`
* 如果要对表名、列名进行动态设置,只能使用`${}`进行sql拼接。
-
parameterType
-
用于设置参数类型,该参数可以省略。
-
我们如果要用的话一般就是
<select id="selectById" parameterType="int" resultType="com.itheima.pojo.Brand"> select * from tb_brand where id=#{id}; </select> -
但是实际上我们方法已经写在对应的Mapper接口中了,mybatis完全能够通过底层反射来获取参数类型,所以基本上很少写
parameterType
-
-
SQL语句中特殊字符的处理
比如:查询小于指定id的品牌
下面的代码就是个错误的示范,因为在.xml文件中, <符号实际上是其标签的起点,而不是作为字符小于号。
<select id="selectById" resultType="com.itheima.pojo.Brand">
select * from tb_brand where id < #{id};
</select>
这时候有两种解决方式
-
直接使用转义字符。比如说小于号
<其转义字符就是< -
使用CDATA区,比如:
<select id="selectById" resultType="com.itheima.pojo.Brand"> select * from tb_brand where id <![CDATA[ < ]]> #{id}; </select>
将特殊字符写在 <![CDATA[ …… ]]>之中,这样就会将CDATA区中的内容作为字符串来进行解析
多条件查询
完成需求: 通过数据库中status、company_name、brand_name 三个字段,查询中符合条件的品牌。
1、散装参数进行查询
首先看BrandMapper.xml 。实际上在sql语句层面,并没有什么区别,仍然是使用 #{}来进行参数值的注入。
<select id="selectByCondition" resultType="com.itheima.pojo.Brand">
select * from tb_brand
where status =#{status}
and company_name like #{companyName}
and brand_name like #{brandName}
</select>
最主要的区别在于BrandMapper接口中关于方法的书写:
因为是有多个参数,所以mybatis实际上并不知道我们要将哪个参数对应到sql语句的具体哪个参数。所以这时候就要用 @Param("xxx") 来进行标明。这里的 xxx 要和 上面xml文件中 #{} 里面的名称完完全全保持一致。
Brand selectByCondition(@Param("status")int status,@Param("companyName")String companyName,@Param("brandName")String brandName);
额外多说一句,因为我们sql语句用了模糊查询like ,那么我们传参进来实际上是要进行处理的,比如说传入的companyName 应该是 %华为% (即要加上占位符,至于是 %还是 _ ,视自己需求而定)。
2、实体类封装参数
这里由于BrandMapper.xml中的代码没有任何变化,我就不重复贴了。
关于实体类封装参数:实际上只需要保证SQL中 放入到 #{}里面的参数名和实体类属性名完完全全对应上即可设置成功。
此时BrandMapper接口中写法如下:
Brand selectByCondition(Brand brand);
由于Brand类中成员对象——公司名 设定成了 companyName 所以.xml文件的sql语句中也要写 #{companyName}
3、map集合
只需要保证sql中的参数名称和map集合的键的名称对应上,即可设置成功。
插入语句
首先还是常规操作。
1、BrandMapper接口中设定好方法
void addBrand(Brand brand);
2、在BrandMapper.xml文件中生成好对应的statement以及sql语句
<insert id="addBrand">
insert into tb_brand (brand_name,company_name,ordered,description,status)
values (#{brandName},#{companyName},#{ordered},#{description},#{status})
</insert>
3、编写测试方法然后执行
通过执行下面的代码,会存在一个很有意思的问题!!!那就是,测试方法正常跑通,也没有报错,但是实际一看数据库中的表数据,却什么变化都没有。
String brandName="金立手机";
String companyName = "金立";
int ordered = 100;
String description="这手机不错";
int status=1;
Brand brand=new Brand();
brand.setBrandName(brandName);
brand.setCompanyName(companyName);
brand.setOrdered(ordered);
brand.setDescription(description);
brand.setStatus(status);
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取sqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
BrandMapper brandMapper =sqlSession.getMapper(BrandMapper.class);
brandMapper.addBrand(brand);
sqlSession.close();
实际上出现数据库表没变化(即没有插入数据)的原因在于 获取sqlSession对象这一行代码 SqlSession sqlSession = sqlSessionFactory.openSession();
实际上这一行代码是可以传入参数的,即
//开启事务,需手动提交事务
SqlSession sqlSession = sqlSessionFactory.openSession(false);
//----------
//关闭事务,是自动提交事务
SqlSession sqlSession = sqlSessionFactory.openSession(true);
如果像我们之前一样不传参,那么就默认是传入的是false,此时就需要手动提交事务。
只需要在sqlSession.close()前面加入一行代码 sqlSession.commit();
ps:如果想在插入数据库之后,能够将自动递增的id主键注入到对象brand中,只需要将.xml中的代码这样写即可
<insert id="addBrand" useGeneratedKeys="true" keyProperty="id">
insert into tb_brand (brand_name,company_name,ordered,description,status)
values (#{brandName},#{companyName},#{ordered},#{description},#{status})
</insert>
删除语句差不多,就不具体介绍了。
Mybatis参数传递
ps:如果真的是通过collection来传参,那么:
接口中的代码:
public interface MyMapper {
int insertUsers(@Param("users") Collection<User> users);
}
.xml文件中的代码
<mapper namespace="com.example.mapper.MyMapper">
<insert id="insertUsers">
INSERT INTO users (username, age)
VALUES
<foreach collection="users" item="user" separator=",">
(#{user.username}, #{user.age})
</foreach>
</insert>
</mapper>
注解完成增删改查
直接说注解的应用:
- 查询:
@Select - 添加:
@Insert - 修改:
@Update - 删除:
@Delete
@Select("select * from tb_brand where id = #{id}")
public User selectById(@param("id")int id);
写在前面: 人家mybatis官网也说了—— 使用注解来映射简单语句会使代码显得更加简洁,但是对于稍微复杂一点的语句,这时候再来使用注解就可能又不好用,又不好看了。