Mybaits
[问题]:
对多查询没有映射成List!
注意事项:
- 标签内部的语句一定要避免在语句后面使用标注
准备工作
依赖导入
pom.xml
<!--myBatis驱动-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.16</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 实体类(对象映射)——非必需(主要是不用再创建数据库)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
或者
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
常用目录结构
------------------------
|---com.example\
| |---pojo\
| |---mapper\
| |---service\
|---resources\
| |---mapper\
| | |---xxmapper.xml
| |---mybatis-config.xml
----------------------
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">
<!--namespace为接口的全限定符-->
<mapper namespace="">
<!-- 声明sql语句-->
<!-- 每个标签为一个接口的实现-->
<!-- resultType是返回的实体对象-->
<select id="methodName" resultType="returnType">
# sql语句
</select>
</mapper>
[!WARNING]
Mapper接口不能重载,仅仅只是根据接口方法名进行标识
Mybatis配置文件
配置的基本结构:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
数据库连接信息,性能配置,mapper.xml配置等等
<?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">
<configuration>
<!-- 配置环境-->
<environments default="mysql">
<!-- 配置mysql环境
id要与default一致
-->
<environment id="mysql">
<!-- 配置事务管理的类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接池-->
<dataSource type="POOLED">
<!-- 配置连接数据库的基本信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatislearning"/>
<property name="username" value="root"/>
<property name="password" value="55646478aas"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<mapper resource="mapper/studentMapper.xml"/>
</mappers>
</configuration>
[!note]
环境配置
<environments>
- 事务管理配置transactionManager:type=[JDBC|MANAGED]
- 数据源配置dataSource:type=[UNPOOLED|POOLED|JNDI]
运行时的功能设置
<settings>
映射器配置**
<mappers>
**
执行的基本流程
加载配置文件->根据配置文件创建会话工厂->工厂开启会话->会话穿件代理对象->代理对象执行方法->会话提交->会话关闭
[!note]
只有增、删、改才需要提交
只有查才会返回实体对象
//配置文件路径
String xmlPath = “”; //resoures为根
//加载配置文件
InputStream resourceAsStream = Resources.getResourceAsStream(studentXML);
//根据配置文件穿件绘画工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//工厂开启绘画
SqlSession sqlSession = sessionFactory.openSession();
//获取mapper接口的代理对象(动态代理)
StudentsMapper mapper = sqlSession.getMapper(StudentsMapper.class);
//执行方法
mapper.method();
//会话提交内容
sqlSession.commit();
//关闭会话
sqlSession.close();
[!note]
ibatis
- 不用创建mapper接口
- 在调用的时候不用创建Mapper代理对象,直接用sqlSeesion执行标签的sql语句(namespace.id,object)(需要整合参数,返回全是Object)
- 提供的CRUD方法实际上是通过Id找到对应的标签,获取sql语句然后执行,将结果返回
创建实体类(自动生成表)
准备Mapper接口和MapperXML文件
在xml文件中专门写sql语句
[!NOTE]
Mybatis中的Mapper接口相当于Dao,区别在与Mapper仅仅值是规定了接口,不需要提供实现类,具体SQL语句写在Mapper文件中
- 这个过程使用了接口的代理
测试
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.springframework.context.annotation.Bean;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.junit.jupiter.api.Test;
import org.zhuhaihong.entity.Student;
import org.zhuhaihong.mapper.StudentsMapper;
import java.io.IOException;
import java.io.InputStream;
//@SpringJUnitConfig
public class TestDemo {
@Test
public void testDemo1() throws IOException {
//配置文件
String studentXML ="mybatis-conf.xml";
//加载配置文件
InputStream resourceAsStream = Resources.getResourceAsStream(studentXML);
//根据配置文件穿件绘画工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//工厂开启绘画
SqlSession sqlSession = sessionFactory.openSession();
//获取mapper接口的代理对象(动态代理)
StudentsMapper mapper = sqlSession.getMapper(StudentsMapper.class);
//代理对象执行sql语句(xml/接口中的方法)
Student student = mapper.selectById(1L);
System.out.println(student);
//会话提交增删改
sqlSession.commit();
//关闭会话
sqlSession.close();
}
}
基本功能
自动提交
增加、删除、修改一定要提交数据,或者开启自动提交
//开启会话
SqlSession sqlSession = build.openSession(true);
日志输出
在配置文件中的settings标签设置
<!--运行时功能设置-->
<settings>
<!-- 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
向SQL传参
- #{参数名}
-
表示占位符+赋值
-
可以防止注入攻击
-
只能代替值,不能替代容器名(标签,列名,sql关键字)
<select id="selectById1" resultType="org.zhuhaihong.entity.Student"> select * from student where id = #{id} </select>
==> Preparing: select * from student where id = ? #占位 ==> Parameters: 1(Long) #传参 <== Columns: id, name, age <== Row: 1, hellen, 19 <== Total: 1
- ${参数名}
-
字符串拼接
-
会发生注入攻击
-
能够代替容器名
<select id="selectById2" resultType="org.zhuhaihong.entity.Student"> select student.id, student.name, student.age from student where id = ${id} </select>
==> Preparing: select student.id, student.name, student.age from student where id = 2 #直接拼接 ==> Parameters: <== Columns: id, name, age <== Row: 2, liutao, 20 <== Total: 1
数据输入
- 使用#{key}或者${key}
简单类型(单值)
-
**单个参数:**传入的单个简单类型,key随意写
-
多个参数:
传入多个根据key名称使用参数名进行对应(不可以)-
方式一:@param指定参数名称**[推荐]**
Student selectByNameAndAge1(@Param("name") String name, @Param("age") int age);
<select id="selectByNameAndAge1" resultType="org.zhuhaihong.entity.Student"> select student.name, student.age from student where name = #{name} and age = #{age} </select>
==> Preparing: select student.name, student.age from student where name = ? and age = ? ==> Parameters: liutao(String), 20(Integer) <== Columns: name, age <== Row: liutao, 20 <== Total: 1 Student(Id=null, name=liutao, age=20)
-
方式二:mybaits默认机制——参数顺序的参数名:arg[0],arg[1]…/param1,param2…
Student selectByNameAndAge2(String name,int age);
<select id="selectByNameAndAge2" resultType="org.zhuhaihong.entity.Student"> select student.name, student.age from student where name = #{arg[0]} and age = #{arg[1]} </select>
==> Preparing: select student.name, student.age from student where name = ? and age = ? ==> Parameters: hellen(String), 19(Integer) <== Columns: name, age <== Row: hellen, 19 <== Total: 1 Student(Id=null, name=hellen, age=19)
-
复杂类型(多值)
-
**类对象:**传入类对象,key使用类的属性名
int insertStudent(Student student);
<insert id="insertStudent"> insert into student(name, age) values (#{name}, #{age}) </insert>
Student student1 = new Student(); student1.setName("dearfriend"); student1.setAge(11); mapper.insertStudent(student1);
-
map类型:key为Map的Key
int insertStudent1(Map<String,String> map);
<insert id="insertStudent1"> insert into student(name, age)values (#{name}, #{age}) </insert>
HashMap<String, String> stringStringHashMap = new HashMap<>(); stringStringHashMap.put("name","hahah"); stringStringHashMap.put("age","15"); System.out.println(stringStringHashMap); mapper.insertStudent1(stringStringHashMap);
{name=hahah, age=15} ==> Preparing: insert into student(name, age)values (?, ?) ==> Parameters: hahah(String), 15(String) <== Updates: 1
-
List类型:动态SQL语句foreach拼接
collection标签传入参数指定(collection属性):
- 列表使用类似简单类型形参传入:@param、arg[x]
- 使用list、conllection专用名词代表形参列表中的集合类型
内部列表的使用——collection标签中item属性定义列表元素名称:
- 简单类型:#{itemName}
- 复杂对象类型: #{itemName.fildeName}
[!CAUTION]
此处虽然数据库中的age为int,但是在传入值的时候会自动转换;如果为不能自动转换的值就会报错
-
数据输出
-
增删改都是int(影响行数)
-
查询返回为实体对象名,mapperxml中只需要指定实体类的类型,不需要考虑返回对象的数量取List等
单值简单类型
- 单个值类型,直接使用数据类型
单值类类型
-
返回自定义类类型:保证列名和类属性名对应
-
默认驼峰和蛇形自动映射:需要手动开启设置:mapUnderscoreToCamelCase
<setting name="mapUnderscoreToCamelCase" value="true"/>
-
查询的sql语句进行从命名与类属性对应
-
map类型
-
传出为map(不指定实体类的情况下)
Map selectS(String name);
<select id="selectS" resultType="map"> select name,age from student where name = #{name} </select>
Map liutao = mapper.selectS("liutao"); System.out.println(liutao);
==> Preparing: select name,age from student where name = ? ==> Parameters: liutao(String) <== Columns: name, age <== Row: liutao, 20 <== Total: 1 {name=liutao, age=20}
多值
- 传出List:只需要指定元素的数据类型(查询结果单行数据类型——泛型)(底层都是使用selectList进行查询,不管是selectOne还是其他)
[!note]
resultType可以使用别名(mybatis提供,基本类型用_int,类用小写首字母),不实用全限定副
自定义类可以在config.xml中使用****设置别名
方式一:类别名
<!-- 定义类的别名(传出参数)--> <typeAliases> <typeAlias type="org.zhuhaihong.entity.Student" alias="student"/> </typeAliases>
方式二:包别名,最后包下面的类的首字母小写为别名
<typeAliases> <typeAlias type="org.zhuhaihong.entity"/> </typeAliases>
方式三:指定了包别名之后,可以使用@Alisas(“别名”).必须和批量注解一起使用
如果接口定义使用列表接受,resultType只需要指定接受元素的类类型
接口直接定义的事单个对象,如果查询到多个,则会报错
接受值的类属性名一定要和列名对应**(sql重命名或者开启驼峰映射)**
理解:将查询到的结果,按照resultType创建对象(没有实体类就使用map),按照列名给对象赋值(每条数据代表一个对象,多个值要使用List,resultType为每条数据的接值类型),如果没有则不进行赋值(null)
List<StudentName> selectName(int age);
<select id="selectName" resultType="org.zhuhaihong.entity.StudentName"> select name from student where age > #{age} </select>
List<StudentName> studentNames = mapper.selectName(10); System.out.println(studentNames)
public class StudentName { private String Aname; //没对应 }
[null, null, null, null, null, null, null, null, null]
public class StudentName { private String name; //对应 }
[StudentName(name=hellen), StudentName(name=liutao), StudentName(name=Liming), StudentName(name=dearfriend), StudentName(name=hahah), StudentName(name=dearfriend), StudentName(name=hahah), StudentName(name=dearfriend), StudentName(name=hahah), StudentName(name=dearfriend), StudentName(name=hahah)]
主键回显
-
主键回显(插入、删除、修改):默认只返回影响行数(int)
-
主键回显是将插入后的主键值赋值到插入时指定的对象属性上,而不是直接返回主键值到返回值上,返回值永远是影响行数
-
自增长
int insertStudentId(Student s);
<insert id="insertStudentId" useGeneratedKeys="true" keyColumn="id" keyProperty="id"> insert into student(name, age) values (#{name}, #{age}) </insert>
//执行 Student student = new Student(); student.setAge(15); student.setName("主键回显"); System.out.println("插入前studentId:"+student.getId()); int i = mapper.insertStudentId(student); System.out.println("插入后后studentId:"+student.getId()); //回显到实体对象 System.out.println(i);
useGeneratedKeys
:是否要使用主键值keyColumn
:主键的列名keyProperty
接受主键实体类的属性名
-
非自增长:主要的难点不在与获取主键值,而在与维护主键(保证不重复)
-
java调用UUID,得到主键值,传入sql
int insert1(Teacher t);
<insert id="insert1"> insert into teacher(id, name) values (#{id}, #{name}) </insert>
//外部生成主键值 Teacher teacher = new Teacher(); String s = UUID.randomUUID().toString().replaceAll("-", ""); teacher.setId(s); teacher.setName("外部主键"); int i = mapper.insert1(teacher); System.out.println(teacher);
==> Preparing: insert into teacher(id, name) values (?, ?) ==> Parameters: 5e0c2eea2e6042398af48d91223d819b(String), 外部主键(String) <== Updates: 1 Teacher(id=5e0c2eea2e6042398af48d91223d819b, name=外部主键)
-
mybatis维护:插入前先指定一段sql语句,生成主键值
int insert(Teacher t);
<insert id="insert"> # 生成主键值 <selectKey order="BEFORE" resultType="string" keyProperty="id"> select replace(UUID(),'-','') </selectKey> insert into teacher(id, name) values (#{id}, #{name}) </insert>
//内部主键 Teacher teacher1 = new Teacher(); teacher1.setName("内部主键"); //不设置主键值 int insert = mapper.insert(teacher1); System.out.println(teacher1);
==> Preparing: select replace(UUID(),'-','') ==> Parameters: <== Columns: replace(UUID(),'-','') <== Row: fa4fe36c526311ef9b32a5809259071a <== Total: 1 ==> Preparing: # 生成主键值 insert into teacher(id, name) values (?, ?) ==> Parameters: fa4fe36c526311ef9b32a5809259071a(String), 内部主键(String) <== Updates: 1 Teacher(id=fa4fe36c526311ef9b32a5809259071a, name=内部主键)
-
使用标签在插入语句前生成主键
-
order
:BEFORE|AFTER——在插入执行前还是后执行 -
resultType
:语句返回类型 -
keyProperty
:给哪个属性赋值(下面的语句参数名)
-
-
[!CAUTION]
如果有这个语句,即使在外部指定了主键值,selectKey同样会将自己的值传入,并且将至返回给对象.
主键回显是将插入后的主键值赋值到插入时指定的对象属性上,而不是直接返回主键值到返回值上,返回值永远是影响行数
自定义映射关系和resultMap
解决实体属性名和表列名不一致的方法总结
-
sql语句查询时对列名取别名和类的属性名统一
-
开启驼峰映射[见单值类类型],只能映射一层结构,从第二层就不映射了,按照列名查找
-
reultMap自定义映射 可以深层映射
<!-- resultMap映射--> <!-- 定义映射关系--> <resultMap id="retM" type="org.zhuhaihong.entity.Teacher"> <id column="id" property="id"/> <result column="name" property="name"/> </resultMap> <!--使用resultMap接受值--> <select id="select" resultMap="retM"> select id, name from teacher where name = #{name} </select>
- :定义映射关系
- id:resultMap的标识
- type:对应的类的类型
- :主键映射关系
- column:表的列名
- property:指定类的对应属性名
- :普通映射关系
- reusutlMap:引用的resultMap的标识
- :定义映射关系
多表映射
- 编写多表的连接查询
- 设计存储数据的实体类,承接多表查询结果
- 定义结果集映射(resultMap)
实体类设计
多表关系:
- 一对一
- 一对多|多对一
- 多对多
查询关系:
- 对一
- 对多
对一
类中类中包含对方对象类型即可
public class Customer {
private Integer customerId;
private String customerName;
private Integer orderId;
}
public class OrderVO {
private Integer orderId;
private String orderName;
private Integer customerId;
private Customer customer; //对方
}
对多
类中类中包含对方对象类型的集合
public class Order {
private Integer orderId;
private String orderName;
private Integer customerId;
}
public class CustomerVO {
private Integer customerId;
private String customerName;
private Integer orderId;
private List<Order> orders; //对方
}
[!IMPORTANT]
- 无论多少张表,实体类设计都是两两考虑
- 查询映射的时候,只需要关注本次查询相关属性,其他的属性为null就行(防止死循环)
多表查询
方式一:手动配置映射resultMap
对一:
自定义实体类order.java
@Data
public class Order {
private Integer orderId;
private String orderName;
private Integer customerId;
// 对一查询
private Customer customer;
}
public interface OrderMapper {
Order selectCustomer(Integer orderId);
}
<!--自定义映射关系-->
<resultMap id="orderCustomer" type="org.zhuhaihong.entity.Order">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<result column="customer_id" property="customerId"/>
<association property="customer" javaType="org.zhuhaihong.entity.Customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
<!-- 这里面的订单好和订单列表就不需要了,就不建立映射了-->
</association>
</resultMap>
<!--使用映射-->
<select id="selectCustomer" resultMap="orderCustomer">
select * from orders join customer on orders.customer_id = customer.customer_id where orders.order_id = #{id}
</select>
//测试
@Test
public void testToOne() throws IOException {
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-conf.xml")).openSession(true);
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
Order order = mapper.selectCustomer(1);
System.out.println(order);
sqlSession.close();
}
==> Preparing: select * from orders join customer on orders.customer_id = customer.customer_id where orders.order_id = ?
==> Parameters: 1(Integer)
<== Columns: order_id, order_name, customer_id, customer_id, customer_name, order_id
<== Row: 1, order1, 1, 1, cus1, 1
<== Total: 1
Order(orderId=1, orderName=order1, customerId=1, customer=Customer(customerId=1, customerName=cus1, orderId=null, orders=null))
对多:
@Data
public class Customer {
private Integer customerId;
private String customerName;
private Integer orderId;
// 对多查询
private List<Order> orders;
}
public interface customerMapper {
Customer listOrder(Integer id);
}
<resultMap id="toMany" type="org.zhuhaihong.entity.Customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
<collection property="orders" ofType="org.zhuhaihong.entity.Order">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
</collection>
</resultMap>
<select id="listOrder" resultMap="toMany">
select * from customer join orders on customer.customer_id = orders.customer_id where orders.customer_id = #{id};
</select>
@Test
public void testToMany() throws IOException {
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-conf.xml")).openSession(true);
customerMapper mapper = sqlSession.getMapper(customerMapper.class);
Customer customer = mapper.listOrder(1);
System.out.println(customer);
System.out.println(customer.getCustomerId());
System.out.println(customer.getCustomerName());
System.out.println("订单:");
for (Order order:customer.getOrders()){
System.out.println(order.getOrderId());
System.out.println(order.getOrderName());
}
sqlSession.close();
}
==> Preparing: select * from customer join orders on customer.customer_id = orders.customer_id where orders.customer_id = ?;
==> Parameters: 1(Integer)
<== Columns: customer_id, customer_name, order_id, order_id, order_name, customer_id
<== Row: 1, cus1, 1, 1, order1, 1
<== Row: 1, cus1, 1, 2, order2, 1
<== Total: 2
Customer(customerId=1, customerName=cus1, orderId=null, orders=[Order(orderId=1, orderName=order1, customerId=null, customer=null)])
1
cus1
订单:
1
order1
自动映射功能设置
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
-
配置:
<!-- 开启深层自动映射--> <setting name="autoMappingBehavior" value="FULL"/>
-
resultMap只需要指定id就行(不需要指定普通字段)
<resultMap id="toMany" type="org.zhuhaihong.entity.Customer"> <id column="customer_id" property="customerId"/> <collection property="orders" ofType="org.zhuhaihong.entity.Order"> <id column="order_id" property="orderId"/> </collection> </resultMap>
<resultMap id="orderCustomer" type="org.zhuhaihong.entity.Order"> <id column="order_id" property="orderId"/> <association property="customer" javaType="org.zhuhaihong.entity.Customer"> <id column="customer_id" property="customerId"/> </association> </resultMap>
总结
- 一定要弄清楚查询返回的是什么数据类型,查询主体是谁.
- 对一的类属
- 属性名
- 属性的类型
- 对多的类属性(列表)
- 属性名
- 元素类型
- 对多根据查询对象的主键值进行映射(对多元素的放在列表中)
动态SQL语句
[!important]
所有的标签使用都以拼接的思维去理解
所以只要满足标签的基本原理,可以随意拼接
- if多条件使用and/or
- trim在prefixOverrrides 、suffixOverrides中都可以指定多个值,用 | 来分隔
[场景]
在查询的时候动态设置查询条件(动态选择参与条件).
- 如果传入属性,就判断条件,如果没有传入就不添加对应的条件
- 原理:sql字符串拼接
if-where标签
- if标签:条件判断,满足条件进行拼接
<if test = "逻辑表达式">
#sql语句
</if>
#逻辑表达式
> >
<<
满足test条件则将sql语句拼接.
- 若都不满足,可能会出现sql多了where关键子
- 解决,使用标签:
- 自动添加where关键字
- 自动去掉多余的and/or
- 自动添加**
,
**
List<Student> queryCondition(Student student);
<select id="queryCondition" resultType="student">
select * from student
<where>
<if test="id !=null">
and id = #{id}
</if>
<if test="name!=null">
and name = #{name}
</if>
<if test="age !=null">
or age = #{age}
</if>
</where>
</select>
Student student = new Student();
List<Student> students2 = mapper.queryCondition(student);
// ==>Preparing:
// select * from student
// ==>Parameters:
student.setName("liutao");
List<Student> students = mapper.queryCondition(student);
// ==> Preparing: select * from student WHERE name = ?
// ==> Parameters: liutao(String)
student.setAge(19);
List<Student> students1 = mapper.queryCondition(student);
// ==> Preparing: select * from student WHERE name = ? or age = ?
// ==> Parameters: liutao(String), 19(Integer)
[!warning]
- 一定不要加**
,
** 条件之间不存在**,
**- 一定要写or/and,以此表明该语句条件是或还是与(最好都加上,where自动管理)
- if也是用key(参考传入参数)接受值,直接用key进行判断
set-if标签
[场景]
更新数据,字段满足条件再更新某些字段
- 包围条件拼接语句:
- 自动添加set关键字,
- 自动去掉多余的**
,
**(一定要添加) - 一定不要加and/or,set语句没有
int update(@Param("age") Integer age, @Param("name") String name,@Param("ageC") Integer ageC, @Param("nameC") String nameC);
<!-- set-if-->
<update id="update">
update student
<set>
<if test="age!=null">
age = #{age},
</if>
<if test="name!=null">
name = #{name},
</if>
</set>
<where>
<if test="nameC!=null">
and name = #{nameC}
</if>
<if test="ageC!=null">
and name = #{ageC}
</if>
</where>
</update>
mapper.update(10,"update1",1,"a1");
// ==> Preparing: update student SET age = ?, name = ? WHERE name = ? and name = ?
// ==> Parameters: 10(Integer), update1(String), a1(String), 1(Integer)
mapper.update(11,null,null,"a2");
// ==> Preparing: update student SET age = ? WHERE name = ?
// ==> Parameters: 11(Integer), a2(String)
[!warning]
- 一定要添加**
,
set只会自动去掉**多余的,不会自动添加(前后都可以)- set一定不要添加and/or,set语句中没有
trim标签
-
自定义——内部满足条件拼接sql语句前,前缀后缀的处理(增加或者删除)
-
可以利用这个编写set和where
规则
- prefix:表示在 trim 标签内的 sql 语句加上前缀
- suffix:表示在 trim 标签内的 sql 语句加上后缀
- prefixOverrides:表示去除第一个前缀
- suffixOverrides:表示去除最后一个后缀
List<Student> queryConditionTrim(Student student);
<!--trim where-->
<select id="queryConditionTrim" resultType="student">
select * from student
<trim prefix="where" prefixOverrides="and|or" suffix=";">#在包围的sql语句加上前缀where,去掉前缀and,加上;后缀
<if test="id!=null">
and id = #{id}
</if>
<if test="name !=null ">
and name = #{name}
</if>
<if test="age!=null">
and age >#{age}
</if>
</trim>
</select>
Student student = new Student();
System.out.println(mapper.queryConditionTrim(student));
// ==> Preparing: select * from student
// ==> Parameters:
student.setId(1L);
System.out.println(mapper.queryConditionTrim(student));
// ==> Preparing: select * from student where id = ? ;
// ==> Parameters: 1(Long)
student.setId(null);
student.setAge(3);
System.out.println(mapper.queryConditionTrim(student));
// ==> Preparing: select * from student where age >? ;
// ==> Parameters: 3(Integer)
student.setName("liutao");
System.out.println(mapper.queryConditionTrim(student));
// 由此可看出trim包围的所有sql语句先进行拼接,内部sql拼接完成后先使用trim规则对整个sql语句前缀后缀处理后再凭借到外部 的sql语句
// ==> Preparing: select * from student where name = ? and age >? ;
// ==> Parameters: liutao(String), 3(Integer)
[!important]
trim
标签是对包围的内容预拼接后的sql语句进行处理,然后再进行最终拼接- 如果内部没有sql语句,则不会增加前缀
- prefix:表示在 trim 标签内的 sql 语句加上前缀
- suffix:表示在 trim 标签内的 sql 语句加上后缀
- prefixOverrides:表示去除第一个前缀
- suffixOverrides:表示去除最后一个后缀
chooes-when-otherwise
- 和if作用相同,此为多分支
- 可以用来设置条件的优先级,有高的优先级的sql语句拼接,低优先级条件不生效
- 根据不同的条件选择执行不同的 SQL 片段
- 多分支语句:从上倒下依次判断条件,满足的话执行内部sql语句凭借,后续不在执行(参考switch-case-break-otherwise)
/*choose-when-otherwise*/
List<Student> queryConditionChoose(Student student);
<!--choose-when-otherwise-->
<!--有id根据id搜索,无ID根据那么搜索,无name根据age搜索,无age默认输出age>10-->
<select id="queryConditionChoose" resultType="student">
select * from student
<where>
<choose>
<when test="id!=null">
id = #{id}
</when>
<when test="name!=null">
and name = #{name}
</when>
<when test="age!=null">
and age>#{age}
</when>
<otherwise>
age>10;
</otherwise>
</choose>
</where>
</select>
Student student = new Student();
//默认条件
System.out.println(student);
mapper1.queryConditionChoose(student);
// Student(Id=null, name=null, age=null)
// ==> Preparing: select * from student WHERE age>10;
// ==> Parameters:
// <== Columns: id, name, age
// <== Row: 1, hellen, 19
// <== Row: 2, liutao, 20
// <== Row: 3, Liming, 21
// <== Row: 4, dearfriend, 11
// <== Row: 8, hahah, 15
// <== Row: 24, a2, 11
// <== Row: 43, a2, 11
// <== Total: 7
//只根据age搜索
student.setAge(15);
System.out.println(student);
mapper1.queryConditionChoose(student);
// Student(Id=null, name=null, age=15)
// ==> Preparing: select * from student WHERE age>?
// ==> Parameters: 15(Integer)
// <== Columns: id, name, age
// <== Row: 1, hellen, 19
// <== Row: 2, liutao, 20
// <== Row: 3, Liming, 21
// <== Total: 3
//只根据name搜索
student.setName("liutao");
System.out.println(student);
mapper1.queryConditionChoose(student);
// Student(Id=null, name=liutao, age=15)
// ==> Preparing: select * from student WHERE name = ?
// ==> Parameters: liutao(String)
// <== Columns: id, name, age
// <== Row: 2, liutao, 20
// <== Total: 1
//只根据id搜索
student.setId(1L);
System.out.println(student);
mapper1.queryConditionChoose(student);
// Student(Id=1, name=liutao, age=15)
// ==> Preparing: select * from student WHERE id = ?
// ==> Parameters: 1(Long)
// <== Columns: id, name, age
// <== Row: 1, hellen, 19
// <== Total: 1
foreach标签
批量增删改查:找单条语句的重复项,找不出来可以直接遍历整条语句
- :批量标签
- collection:要便利的集合
- item:集合便利过程中的每一个对象,在此属性设置对象名称,后面使用此名称引用对象
- Separartor:指定批拼接sql字符串的时候,各条语句之间的分割符
- open:循环拼接字符串后,在整体前面添加的字符串
- close:循环拼接字符串后,在整体后面添加的字符串
- index: 在list和array中,index指元素序号;在map中,index指元素key。从0开始自增(相当于数组下标)
/*foreach*/
int insertBatch(List<Student> students);
<!--insert foreach-->
<!-- collection = "collection"/arg[x]/接口使用@param定义的名称-->
<insert id="insertBatch" >
insert into student(name, age)
<foreach collection="list" item="itemName" open="Values" close=";" separator=",">
(name = #{itemName.name},age = #{itemName.age})
</foreach>
</insert>
Student student1 = new Student();
student1.setName("111");
student1.setAge(111);
Student student2 = new Student();
student2.setName("222");
student2.setAge(222);
Student student3 = new Student();
student3.setName("333");
student3.setAge(333);
ArrayList<Student> students = new ArrayList<>();
students.add(student1);
students.add(student2);
students.add(student3);
mapper.insertBatch(students);
// ==> Preparing: insert into student(name, age) Values (name = ?,age = ?) , (name = ?,age = ?) , (name = ?,age = ?) ;
// ==> Parameters: 111(String), 111(Integer), 222(String), 222(Integer), 333(String), 333(Integer)
// <== Updates: 3
[!IMPORTANT]
传入List类型的时候,mybatis不支持形参名称索引,只能使用如下方式表示传入的列表——放到collection表示列表形参
- @param
- arg[0]…
- list、collection(默认寻找形参列表中的集合)
传入参数的引用使用collection指定的item名称引用,对象列表使用itemName.fildeName传值
[!IMPORTANT]
批量操作存在多条完整的sql语句(一条语句的某个部分重复不算),需要开启允许多条语句执行
url: jdbc:mysql:///database?allowMultiQueries = true
sql片段
提取重复的sql片段:可以很方便的进行批量修改
- 定义sql片段:提取的sql语句
[!WARNING]
id不能与方法名重复
- 使用sql片段:
<!--优化:提取公共sql片段-->
<sql id="select">
select * from student
</sql>
<select id="queryCondition" resultType="student">
# select * from student
<include refid="selectAll"/>
<where>
<if test="id !=null">
and id = #{id}
</if>
<if test="name!=null">
and name = #{name}
</if>
<if test="age !=null">
or age = #{age}
</if>
</where>
</select>
//==> Preparing: # select * from student # select * from student select * from student where id = ? ;
- sql片段会使用#来标注上,不会影响功能
mybatis扩展
批量配置mapper包批量扫描配置
[!NOTE]
resources 和java是同级目录,为类路径的根文件夹,类路径从该路径下开始
要求:
-
MapperXML文件和mapper接口命名相同
-
最终打包(package)位置一致,都在mybaits配置的mapper包下:
-
方式一:xml文件直接放入接口包内
-
方式二:在resource下创建mapper接口包对应的文件夹结构
[!WARNING]
resources目录下不能使用 . 所谓文件夹分割符,只能使用/
-
- 单个mapperXML文件扫描的打包目录结构
批量包扫描配置(接口所在包)
<mappers>
<!-- <mapper resource="mapper/studentMapper.xml"/>-->
<!-- <mapper resource="mapper/teacherMapper.xml"/>-->
<!-- <mapper resource="mapper/orderMapper.xml"/>-->
<!-- <mapper resource="mapper/customerMapper.xml"/>-->
<!-- <mapper resource="mapper/dynamicSql.xml"/>-->
<package name="org.zhuhaihong.mapper"/>
</mappers>
打包目录结构方式一:
配置打包资源(默认不会打包其他文件)
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
目录结构方式二:
插件:对拼接好后的sql’语句进一步处理
分页插件的使用
- 不添加limit
- 不实用;结尾
依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>6.1.0</version>
</dependency>
mybatis配置(注意配置顺序)
<plugins>
<!-- 插件名称-->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 指定数据库类型-->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
使用
List<Student> queryAll();
<!--分页插件使用测试-->
<select id="queryAll" resultType="org.zhuhaihong.entity.Student">
# 一定不能写;
select * from student
</select>
@Test
public void testPageHelper() throws IOException {
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-conf.xml")).openSession(true);
StudentsMapper mapper = sqlSession.getMapper(StudentsMapper.class);
//先设置分页数据
PageHelper.startPage(1,5);
//正常执行
List<Student> students = mapper.queryAll();
//将查询数据放入PageInfo实体类(相当于将查询结果交给pageInfo管理)(不是全部查出,而是将sql语句进行拦截处理)
PageInfo<Student> studentPageInfo = new PageInfo<>(students);
//相关操作
System.out.println("总页数:"+studentPageInfo.getPages());
System.out.println("总条数:"+studentPageInfo.getTotal());
System.out.println("当前页:"+studentPageInfo.getPageNum());
System.out.println("当前页数据:"+studentPageInfo.getList());
}
==> Preparing: select count(0) from ( # 一定不能写; select * from student ) tmp_count
==> Parameters:
<== Columns: count(0)
<== Row: 43
<== Total: 1
==> Preparing: # 一定不能写; select * from student LIMIT ?
==> Parameters: 5(Integer)
<== Columns: id, name, age
<== Row: 1, hellen, 19
<== Row: 2, liutao, 20
<== Row: 3, Liming, 21
<== Row: 4, dearfriend, 11
<== Row: 8, hahah, 15
<== Total: 5
总页数:9
总条数:43
当前页:1
当前页数据:Page{count=true, pageNum=1, pageSize=5, startRow=0, endRow=5, total=43, pages=9, reasonable=false, pageSizeZero=false}
[
Student(Id=1, name=hellen, age=19),
Student(Id=2, name=liutao, age=20),
Student(Id=3, name=Liming, age=21),
Student(Id=4, name=dearfriend, age=11),
Student(Id=8, name=hahah, age=15)
]
相关操作
/**
* 当前页
*/
private int pageNum;
/**
* 每页的数量
*/
private int pageSize;
/**
* 当前页的数量
*/
private int size;
/**
* 由于startRow和endRow不常用,这里说个具体的用法
* 可以在页面中"显示startRow到endRow 共size条数据"
* 当前页面第一个元素在数据库中的行号
*/
private long startRow;
/**
* 当前页面最后一个元素在数据库中的行号
*/
private long endRow;
/**
* 总页数
*/
private int pages;
/**
* 前一页
*/
private int prePage;
/**
* 下一页
*/
private int nextPage;
/**
* 是否为第一页
*/
private boolean isFirstPage = false;
/**
* 是否为最后一页
*/
private boolean isLastPage = false;
/**
* 是否有前一页
*/
private boolean hasPreviousPage = false;
/**
* 是否有下一页
*/
private boolean hasNextPage = false;
/**
* 导航页码数
*/
private int navigatePages;
/**
* 所有导航页号
*/
private int[] navigatepageNums;
/**
* 导航条上的第一页
*/
private int navigateFirstPage;
/**
* 导航条上的最后一页
*/
private int navigateLastPage;