MyBatis框架基础
MyBatis是一款优秀的持久层框架,它能够帮助Java开发者将业务逻辑与数据访问代码分离,具有简单,灵活,高效等特点,MyBatis使用xml或或注解的方式描述Java对象及其sql语句之间的映射关系,它支持多种数据库和操作方式,并且可以满足不同的数据访问要求,
MyBatis与JDBC的关系
JDBC的操作为例横向对比多次数据库访问的代码,
- 每次执行的sql语句不同,或sql语句绑定的参数不同
- 对结果集的操作逻辑不同
MyBatis的主要作用是编写访问数据库代码时,仅需要关注要执行的sql语句,参数与sql的绑定关系以及结果集的处理逻辑,其他部分由MyBatis完成,
MyBatis底层使用的任然是JDBC技术
Springboot项目使用MyBatis框架
1,引入mybatis的依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
2,在springboot中配置mybatis框架
在src/main/resources文件夹中新建application.properties文件,该文件是Spring Boot项目的默认配置文件。在该文件中添加项目的配置信息:
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址==>必须确保数据库中有这个数据库database1,或改为自己的数据库
spring.datasource.url=jdbc:mysql://localhost:3306/database1?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
3,编写Mapper接口
- 使用接口中的抽象方法来声明要执行的数据库操作
4,编写Mapper.xml映射文件或者在接口中添加注释
- Mapper.xml文件用于描述java对象和sql语句之间的映射关系
- 简单的sql语句可以通过在Mapper接口的方法付添加注解来实现
值的传递
在MyBatis中,值的传递主要是在java代码和sql语句之间传递参数值的过程,这些参数值可以单个值,也可以是数组,javabean对象;
- 单值传递
- 对象传递
- Map传值
单值传递
如果程序中只有一个参数需要传递给SQL,可以直接作为方法参数传入。在SQL中可以使用任意名称获取这个参数。虽然名称可以任意,但通常仍然使用该属性的名称,以保证可读性。
例如,开发一个基于用户id查询用户信息的方法,可以在SQL中使用【#{参数名}】的方式来代表要传入的参数,具体的SQL语句如下:
select * from user where username = #{username}
此时,在Mapper接口的抽象方法中,可以声明一个对应类型的参数,名称与SQL中的参数名一致。MyBatis框架会自动获取调用方法时传入的参数,绑定到SQL语句的相应位置。
@Param注解
Mapper接口中某个方法接收了多个参数,需要在每个参数前添加@Param注解,手动添加参数映射的名称,否则会出现【BindingException: Parameter ‘xxx’ not found】的异常。
@Select("select * from user where " +
"username=#{username} and password =#{password}")
User getUser(@Param("username") String username, @Param("password") String password);
对象传值
将多个参数传递给SQL时,可以使用对象来封装数据。
//mapper接口
@Insert("insert into user (username, password, role) " +
"values (#{username}, #{password}, #{role})")
int insertUser(User user);
//测试方法
@Test
void insertUser() {
User user = new User();
user.setUsername("dandy");
user.setPassword("845665");
user.setRole("普通用户");
int row = userMapper.insertUser(user);
System.out.println("row = " + row);
}
#{}和${}
MyBatis中的#{}是用来替换SQL中的参数占位符。在执行SQL语句时,MyBatis会将占位符#{}替换成实际传递的参数值,并将其作为预编译语句的参数传递给数据库引擎。
#{}的核心特点是使用了预编译机制,相当于JDBC中的PreparedStatement:
- 当传入的参数为字符串类型时,生成的SQL语句中会在参数值的前后自动添加单引号
- 当传入的参数中包含SQL的特殊符号时,会被当成普通字符处理,可以避免SQL注入
在 MyBatis 中,${} 是一种参数占位符,用于将输入参数直接插入到 SQL 语句中。
${}的核心特点是没有使用预编译机制,相当于JDBC中的Statement。
- 不论传入的参数是否为字符串类型,生成的SQL语句中都不会自动添加单引号
- 当传入的参数中包含SQL的特殊符号时,特殊符号可能会生效,因此存在SQL注入的风险
- 可以实现某些使用#{}无法实现的功能,如动态选择查询的列名
MyBatis常用标签
Mapper.xml文件
Mapper.xml是MyBatis中定义sql语句映射文件,可以在Mapper.xml文件中,可以指定数据库操作的sql语句,包括增删查改等操作的所有语句,此外,还可以定义查询结果映射规则,将查询结果映射为一个Java对象,
Mapper.xml文件的结构
<?xml version="1.0" encoding="UTF-8" ?>//xml文件声明
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> //文件格式声明
<mapper namespace="">
</mapper>
namespace属性值需要匹配映射的接口位置,mapper标签内部的内容,例如 select 标签,用于接口中的抽象方法提供的映射的sql语句,是Mapper.xml的核心
步骤:
1.新建一个springboot项目,添加MySQL依赖和MyBatis框架的依赖
2.配置application.properties文件,添加相应的配置
3.创建实体类,此类的属性与数据库的某个表对应,注意下换线用驼峰命名
4.创建Mapper接口,声明必要的抽象方法,
添加@Mapper注解
5.在src/main/resorces目录下新建一个mapper文件夹,然后再此文件夹中新建一个Mapper.xml文件,并在文件夹下添加相应的操作数据库的语句
6.测试用例,在Mapper接口中,右键——》Generate——》Test…——》选择相应的抽象方法
添加@SpringBootTest注解和@Autowired(依赖注入)
下划线映射驼峰
数据库的表的字段使用下划线分割,例如 sml_title;此命名会与java的实体类造成不一致,从而导致MyBatis的查询结果属性值为null;
解决方案:
1、配置自动映射。
可以在application.properties文件中添加如下配置,该配置可以自动实现 sml_title到 smlTitle的转换
mybatis.configuration.map-underscore-to-camel-case=true
2.在SQL中通过配置别名手动建立映射。
select sml_title as smlTitle from news where id=#{id}
MyBatis基础标签
增删改查标签
在MyBatis的Mapper.xml中,通过各类标签来定义Mapper接口中的抽象方法映射的SQL语句。这些标签中,最为基础的是实现数据增、删、改、查操作的标签,分别
<insert>、<delete>、<update>、<select>。
共同的属性:
- id:指定标签对应的抽象方法的名称,是标签的必要属性,同一个Mapper.xml文件中不能有两个标签拥有相同的id值
- parameterType:输入参数的类型
- statementType:指定是否使用预处理语句,可选值为 STATEMENT、PREPARED、CALLABLE,默认为 PREPARED
1、select标签
- resultType:指定查询结果的数据类型,适用于查询结果与Java类完全匹配的情况
- resultMap:指定查询结果映射的规则,适用于查询结果不能直接映射到某个Java类的情况
2、insert标签
- useGeneratedKeys:表示是否使用自动生成的主键,默认为false。如果设置为true,则在插入数据时会返回主键值
- keyProperty:表示将自动生成的主键值赋值给哪个属性,需要与useGeneratedKeys一起使用
模糊查询
在MyBatis中可有两种方法实现,
1.传值时在参数前后添加通配符(%),
2.在Mapper.xml的文件中sql语句中用concat函数实现,
select * from product where name like concat('%',#{name},'%')
resultMap标签
当一个查询的结果无法简单的映射到一个Java类时,可以使用resultMap标签来配置查询结果到Java类的映射关系。无法通过下划线到驼峰的转换来建立映射关系。
简单来说:就是当java的实体类的属性与数据库表的字段名无法通过驼峰命名来解决时,java的实体类的属性为 a,可在此标签中用 b 来表示,
1.属性:
- id:ResultMap的唯一标识符,在同一个命名空间下必须唯一
- type:映射的java对象的完全限定名或类型别名
2.子标签:
- id标签:用来指定主键的映射关系的,column属性的值是查询结果集中主键的列名,就是数据库表的字段名,property属性的值是java实体类的属性名
- result标签:用来映射java类的属性和sql查询的结果集的列,column属性的值是查询结果集中主键的列名,就是数据库表的字段名,property属性的值是java实体类的属性名,
在select标签中。不在使用resultType属性,而是使用resultMap属性,属性值与定义好的resultMap标签的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="com.example.boot.mapper1.UserMapper">
<resultMap id="UserMap" type="com.example.boot1.entity.UserLog">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="opt_type" property="optType"/>
<result column="opt_create" property="created"/>
<result column="opt_modified" property="updated"/>
</resultMap>
<!--查询所有用户-->
<!--List<UserLog> listAll()-->
<select id="listAll" resultMap="UserMap">
select * from user
</select>
</mapper>
封装关联查询
association标签
多对一
在 MyBatis 中,association 标签是用于定义对象之间关联关系的标签,主要用于处理多对一的关联查询结果映射。当在查询中需要将结果集中的某些列映射到嵌套的对象属性时,可以使用 association标签来实现。
属性:
- property : 指定java类中的属性名,用于关联查询结果中的对应字段,
- column:指定查询结果集合中的字段名,就是表的字段名
- javaType:指定关联的java类型,代表关联属性的数据类型
association标签中可以添加id和result标签,作用与resultMap标签中的id和result标签相同。但是需要注意,association标签中的id和result标签是用于建立关联表列名到关联类属性名的映射。
Mapper.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.example.boot1.mapper.ProductMapper">
<!--必须包含全部字段,即使列名和属性名一致也得显式声明-->
<resultMap id="ProductMap" type="com.example.boot1.entity.Product">
<id column="id" property="id"/>
<result column="title" property="title"/>
<result column="image" property="image"/>
<result column="discount" property="discount"/>
<result column="price" property="price"/>
<result column="sales" property="sales"/>
<result column="stock" property="stock"/>
<result column="category_id" property="categoryId"/>
<result column="views" property="views"/>
<result column="created" property="created"/>
<result column="updated" property="updated"/>
<association property="category" javaType="com.example.boot1.entity.Category">
<id column="category_id" property="id"/>
<result column="name" property="name"/>
<result column="display" property="display"/>
<result column="order_num" property="orderNum"/>
<result column="mc_created" property="created"/>
<result column="mc_updated" property="updated"/>
</association>
</resultMap>
<!--基于商品id查询商品信息-->
<!--关联查询商品品类信息-->
<!--Product getById(long id)-->
<select id="getById" resultMap="ProductMap">
select t1.id, title, image, discount, price, sales, stock, category_id, views, t1.created, t1.updated,name, display, order_num, t2.created as c_created, t2.updated as c_updated from product t1 join category t2
on t1.category_id = t2.id where t1.id = #{id}
</select>
</mapper>
collection标签
一对多
属性:
- property:表示待映射的Java类中的集合类型属性名称
- ofType:表示集合中元素的Java类型
collection标签中可以添加id和result标签,作用与association标签中的id和result标签相同
Mapper.xml文件与上一个标签的大体一样,只是一个标签不一样
把javaType改为 ofType
复用sql语句
在 MyBatis 中,可以通过使用 sql标签和 include 标签来复用 SQL 语句。这两个标签的组合允许开发者在多个地方定义和引用相同的 SQL 片段,以提高代码的重用性和可维护性。
将Mapper.xml文件中的sql语句抽取出来,放入一个sql标签里面
<sql id="yq">//id的属性名称随意取
select * from newss;
</sql>
//然后再select标签里添加如下,
<select id="" resultMap="">
<include refid="yq"/>
where id=#{id}
</select>
动态SQL
MyBatis动态SQL标签
MyBatis的动态SQL是一种允许在SQL语句中根据不同情况应用不同SQL片段的技术。这种技术可以帮助我们构建更加灵活和可维护的SQL语句,使得我们能够根据不同的查询条件来构建不同的SQL语句,从而避免了写许多冗余的SQL代码。
MyBatis提供了多种动态SQL标签,较为常用的四种如下所示:
1、if标签:用于判断某个条件是否满足,如果满足则执行某些操作。
2、choose标签:类似于Java中的switch语句,根据某个条件选择不同的分支,可以配合when和otherwise标签使用。
3、foreach标签:用于遍历集合(如List、Map等),根据集合中的元素动态构建SQL语句。
4、where标签:用于动态生成where语句,当where语句为空时,不会加入到最终的SQL语句中。
Mapper.xml文件例:if
<!--查询商品列表数据-->
<!--List<Product> listProduct(@Param("title") String title
, @Param("minPrice") Double minPrice
, @Param("maxPrice") Double maxPrice)-->
<select id="listProduct" resultMap="ProductMap">
select * from product
<where>
<if test="title != null and title != ''">
and title like concat('%', #{title}, '%') <!-- 模糊查询 -->
</if>
<if test="minPrice != null">
and price >= #{minPrice}<!-- >是大于号 -->
</if>
<if test="maxPrice != null">
and price <= #{maxPrice}<!-- <是小于号 -->
</if>
</where>
order by price desc
</select>
when标签示例
需求:开发一个查询商品列表的方法,接收字符串类型的orderType作为参数。其中:
- 如果传入的参数为“sales”,则按照销量降序排列
- 如果传入的参数为“views”,则按照浏览量降序排列
- 其他情况下,按stock库存量降序排列
List<Product> listProductByOrder(String orderType);//mapper的接口的抽象方法
//Mapper.xml文件
<!--查询商品列表数据,返回排序后的数据-->
<!--List<Product> listProductByOrder(String orderType)-->
<select id="listProductByOrder" resultMap="ProductMap">
select * from product
order by
<choose>
<when test="orderType == 'sales'">
sales
</when>
<when test="orderType == 'views'">
views
</when>
<otherwise>
stock
</otherwise>
</choose>
desc
</select>
//测试
@Test
void listProductByOrder() {
List<Product> list = null;
list = productMapper.listProductByOrder("");
list.forEach( product -> {
System.out.println(product.getId()+"\t\t"+product.getSales()
+"\t\t"+product.getViews()+"\t\t"+product.getStock());
});
}
foreach标签示例
List<Product> listProductByCategory(@Param("cids") List<Integer> cids);
<!--基于商品类型id的集合查询商品列表数据-->
<!--List<Product> listProductByCategory(@Param("cids") List<Integer> cids)-->
<select id="listProductByCategory" resultMap="ProductMap">
select * from product
where
<choose>
<when test="cids != null and cids.size() > 0">
category_id in
<foreach collection="cids" item="cid" open="(" close=")" separator=",">
#{cid}
</foreach>
</when>
<otherwise>
1 = 2
</otherwise>
</choose>
</select>
@Test
void listProductByCategory() {
List<Product> list = null;
list = productMapper.listProductByCategory(List.of(1, 2));
list.forEach( product -> {
System.out.println(product.getId()+"\t\t"+product.getCategoryId());
});
}