一、MyBatis概念
MyBatis(半自动)是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或者注解来配置和映射原生类型、接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
持久层框架 orm框架(object relation mapping)对象关系映射
java | database |
类 | 表 |
对象 | 一行数据(主键 |
属性 | 列 |
目前主流持久层框架:hibernate(全自动),jpa,springjdbc。
j2ee的三大框架:ssh(struts2+spring+hibernate)
ssm(SpringMVC/SpringBoot+Spring+MyBatis)
ssj(SpringMVC+Spring+Springjdbc)
hibernate和mybatis的区别:hibernate全自动 不需要写sql,缺点,不安全,银行系统不支持全自动。
mybatis半自动 需要写sql。
二、如何使用MyBatis
步骤一:pom.xml文件中引入jar包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
步骤二:配置全局配置文件mybatis-config.xml
每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为核心的。SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得。而SqlSessionFactoryBuilder则可以从XML配置文件或者一个预先配置的Configuration实例来构建出SqlSessionFactory实例。
全局配置文件 mybatis-config.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="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!-- 自己数据库ip 端口号 自己数据库名称-->
<property name="url" value="jdbc:mysql://localhost:3306/cloudwise?useUnicode=true&characterEncoding=utf-8&useSSL=false"/>
<!-- 数据库用户名-->
<property name="username" value="root"/>
<!-- 数据库密码-->
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 映射文件-->
<mappers>
<mapper resource="mapper/UserInfoMapper.xml"/>
</mappers>
</configuration>
第三步:构建 SqlSessionFactory
可以从配置或者直接编码来创建SqlSessionFactory 通过SqlSessionFactory创建SqlSession
package com.aaa.mybatis;
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;
/**
* @version 1.0
* @author:张先生
* @description: 我自己创建sqlsession的工具类
* @date 2022/8/10 15:46
*/
public class MySqlSessionFactory {
static String resource = "mybatis-config.xml";
static SqlSessionFactory sqlSessionFactory;
static {
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
/**
* @create by: Teacher张
* @description: 获取sqlsession
* @create time: 2022/8/10 15:52
* @param
* @return SqlSession
*/
public static SqlSession getSQLSession(){
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
步骤四:创建dao接口
public interface UserInfoMybatisDao {
List<User> findAllUser();
}
步骤五:创建mapper映射文件
<?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.aaa.dao.UserMybatisDao">
<select id="findAllUser" resultType="com.aaa.entity.User">
select * from user
</select>
</mapper>
步骤六:调用session.commit()提交事务
如果是更新、添加、删除语句,我们还需要提交一下事务。
步骤七:调用session.close()关闭会话
最后一定要记得关闭会话。
安装mybatis插件
步骤八:使用junit测试工具单元测试
1.导入junit的jar包
2.在dao接口中生成测试类(alt+insert 点击测试)
package com.aaa.dao;
import com.aaa.entity.User;
import com.aaa.mybatis.MySqlSessionFactory;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
public class UserMybatisDaoTest {
SqlSession sqlSession;
@Before
public void init() throws Exception {
sqlSession = MySqlSessionFactory.getSQLSession();
}
@After
public void aaa() throws Exception {
sqlSession.commit();
sqlSession.close();
}
@Test
public void findAllUser() {
UserMybatisDao mapper = sqlSession.getMapper(UserMybatisDao.class);
List<User> allUser = mapper.findAllUser();
for (User user : allUser) {
System.out.println(user.toString());
}
}
}
步骤九:mybatis配置文件
1.properties
这些属性可以在外部进行配置,并可以进行动态替换。你既可以典型Java属性文件中配置这些属性,也可以在properties元素的子元素中设置。例如
修改配置 对应自己的数据库
2.settings
这是MyBatis中极为重要的调整设置,它们会改变MyBatis的运行时行为。下表描述了设置中各项的意图、默认值等 在xml文件中加上
<settings>
<!-- 关闭二级缓存-->
<setting name="cacheEnabled" value="false"/>
<!-- 开启日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
测试一级缓存
public class UserMybatisDaoTest {
SqlSession sqlSession;
UserDao userDao;
@Before
//测试之前
public void setUp() throws Exception {
sqlSession = MySqlSessionFactory.getSQLSession();
userDao = sqlSession.getMapper(UserDao.class);
}
@After
//测试之后
public void tearDown() throws Exception {
System.out.println("结束测试");
if (sqlSession!=null){
//事务提交
sqlSession.commit();
}else {
//关闭sqlSession
sqlSession.close();
}
}
/* @Test
public void findAllUser() {
UserMybatisDao mapper = sqlSession.getMapper(UserMybatisDao.class);
List<User> allUser = mapper.findAllUser();
for (User user : allUser) {
System.out.println(user.toString());
}
}*/
@Test
/*
mybatis的一级缓存,在同一个sqlsession中生效
一旦sqlsession关闭,缓存消失,查询相同的内容,需要重新连接数据库获取
*/
public void findAllUser(){
// 从测试案例可以看出,同一次会话,默认缓存数据,第二次查询相同的数据,直接从缓存中获取
System.out.println("开始第一次查询");
List<User> userAll1 = userDao.findAllUser();
for (User user : userAll1) {
System.out.println("实体类"+user.toString());
}
System.out.println("第二次查询");
List<User> userAll2 = userDao.findAllUser();
for (User user : userAll2) {
System.out.println("实体类"+user.toString());
}
/*
第三次关闭sqlsession,重新打开一个sqlsession
测试案例可以发现,一旦sqlsession关闭,缓存自动消失,查询相同的内容,需要重新连接数据库获取。
*/
//关闭sqlsession
//sqlSession.close();
//重新打开一个新的sqlsession
/* SqlSession sqlSession1 = MySqlSessionFactory.getSQLSession();
UserMybatisDao mapper = sqlSession1.getMapper(UserMybatisDao.class);*/
System.out.println("第三次查询");
List<User> userAll3 = userDao.findAllUser();
for (User user : userAll3) {
System.out.println("实体类"+user.toString());
}
}
}
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
开始第一次查询
Opening JDBC Connection
Created connection 1624820151.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@60d8c9b7]
==> Preparing: select * from `user`
==> Parameters:
<== Columns: id, name, age, email
<== Row: 1, Jone, 18, test1@baomidou.com
<== Row: 2, Jack, 20, test2@baomidou.com
<== Row: 3, Tom, 28, test3@baomidou.com
<== Row: 4, Sandy, 21, test4@baomidou.com
<== Row: 5, Billie, 24, test5@baomidou.com
<== Total: 5
实体类User{id=1, name='Jone', age=18, email='test1@baomidou.com'}
实体类User{id=2, name='Jack', age=20, email='test2@baomidou.com'}
实体类User{id=3, name='Tom', age=28, email='test3@baomidou.com'}
实体类User{id=4, name='Sandy', age=21, email='test4@baomidou.com'}
实体类User{id=5, name='Billie', age=24, email='test5@baomidou.com'}
第二次查询
实体类User{id=1, name='Jone', age=18, email='test1@baomidou.com'}
实体类User{id=2, name='Jack', age=20, email='test2@baomidou.com'}
实体类User{id=3, name='Tom', age=28, email='test3@baomidou.com'}
实体类User{id=4, name='Sandy', age=21, email='test4@baomidou.com'}
实体类User{id=5, name='Billie', age=24, email='test5@baomidou.com'}
第三次查询
实体类User{id=1, name='Jone', age=18, email='test1@baomidou.com'}
实体类User{id=2, name='Jack', age=20, email='test2@baomidou.com'}
实体类User{id=3, name='Tom', age=28, email='test3@baomidou.com'}
实体类User{id=4, name='Sandy', age=21, email='test4@baomidou.com'}
实体类User{id=5, name='Billie', age=24, email='test5@baomidou.com'}
结束测试Process finished with exit code 0
从上面的测试案例可以看出,同一次会话,默认缓存数据,第二次查询相同的数据,直接从缓存中获取。
public class UserMybatisDaoTest {
SqlSession sqlSession;
UserDao userDao;
@Before
//测试之前
public void setUp() throws Exception {
sqlSession = MySqlSessionFactory.getSQLSession();
userDao = sqlSession.getMapper(UserDao.class);
}
@After
//测试之后
public void tearDown() throws Exception {
System.out.println("结束测试");
if (sqlSession!=null){
//事务提交
sqlSession.commit();
}else {
//关闭sqlSession
sqlSession.close();
}
}
/* @Test
public void findAllUser() {
UserMybatisDao mapper = sqlSession.getMapper(UserMybatisDao.class);
List<User> allUser = mapper.findAllUser();
for (User user : allUser) {
System.out.println(user.toString());
}
}*/
@Test
/*
mybatis的一级缓存,在同一个sqlsession中生效
一旦sqlsession关闭,缓存消失,查询相同的内容,需要重新连接数据库获取
*/
public void findAllUser(){
// 从测试案例可以看出,同一次会话,默认缓存数据,第二次查询相同的数据,直接从缓存中获取
System.out.println("开始第一次查询");
List<User> userAll1 = userDao.findAllUser();
for (User user : userAll1) {
System.out.println("实体类"+user.toString());
}
System.out.println("第二次查询");
List<User> userAll2 = userDao.findAllUser();
for (User user : userAll2) {
System.out.println("实体类"+user.toString());
}
/*
第三次关闭sqlsession,重新打开一个sqlsession
测试案例可以发现,一旦sqlsession关闭,缓存自动消失,查询相同的内容,需要重新连接数据库获取。
*/
sqlSession.close();
//重新打开一个新的sqlsession
SqlSession sqlSession1 = MySqlSessionFactory.getSQLSession();
UserDao mapper = sqlSession1.getMapper(UserDao.class);
System.out.println("第三次查询");
List<User> userAll3 = mapper.findAllUser();
for (User user : userAll3) {
System.out.println("实体类"+user.toString());
}
}
}
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
开始第一次查询
Opening JDBC Connection
Created connection 2035070981.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@794cb805]
==> Preparing: select * from `user`
==> Parameters:
<== Columns: id, name, age, email
<== Row: 1, Jone, 18, test1@baomidou.com
<== Row: 2, Jack, 20, test2@baomidou.com
<== Row: 3, Tom, 28, test3@baomidou.com
<== Row: 4, Sandy, 21, test4@baomidou.com
<== Row: 5, Billie, 24, test5@baomidou.com
<== Total: 5
实体类User{id=1, name='Jone', age=18, email='test1@baomidou.com'}
实体类User{id=2, name='Jack', age=20, email='test2@baomidou.com'}
实体类User{id=3, name='Tom', age=28, email='test3@baomidou.com'}
实体类User{id=4, name='Sandy', age=21, email='test4@baomidou.com'}
实体类User{id=5, name='Billie', age=24, email='test5@baomidou.com'}
第二次查询
实体类User{id=1, name='Jone', age=18, email='test1@baomidou.com'}
实体类User{id=2, name='Jack', age=20, email='test2@baomidou.com'}
实体类User{id=3, name='Tom', age=28, email='test3@baomidou.com'}
实体类User{id=4, name='Sandy', age=21, email='test4@baomidou.com'}
实体类User{id=5, name='Billie', age=24, email='test5@baomidou.com'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@794cb805]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@794cb805]
Returned connection 2035070981 to pool.
第三次查询
Opening JDBC Connection
Checked out connection 2035070981 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@794cb805]
==> Preparing: select * from `user`
==> Parameters:
<== Columns: id, name, age, email
<== Row: 1, Jone, 18, test1@baomidou.com
<== Row: 2, Jack, 20, test2@baomidou.com
<== Row: 3, Tom, 28, test3@baomidou.com
<== Row: 4, Sandy, 21, test4@baomidou.com
<== Row: 5, Billie, 24, test5@baomidou.com
<== Total: 5
实体类User{id=1, name='Jone', age=18, email='test1@baomidou.com'}
实体类User{id=2, name='Jack', age=20, email='test2@baomidou.com'}
实体类User{id=3, name='Tom', age=28, email='test3@baomidou.com'}
实体类User{id=4, name='Sandy', age=21, email='test4@baomidou.com'}
实体类User{id=5, name='Billie', age=24, email='test5@baomidou.com'}
结束测试
从上面的测试案例可以发现,mybatis的一级缓存,在同一个sqlsession中生效,,一旦sqlsession关闭,缓存自动消失,查询相同的内容,需要重新连接数据库获取。
设置参数 | 描述 | 有效值 | 默认值 |
cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关。 | true,false | true |
lazyLoadingEnabled | 懒加载,延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 | true,false | false |
aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 | true,false,true | |
multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要兼容驱动)。 | true,false | true |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true,false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 | true,false | False |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) |
safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。 | true,false | False |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | true, false | False |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION,STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER | OTHER |
lazyLoadTriggerMethods | 指定哪个对象的方法触发一次延迟加载。 | A method name list separated by commas | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定动态 SQL 生成的默认语言。 | A type alias or fully qualified class name. | org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 | true,false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | Any String | Not set |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J,LOG4J,LOG4J2,JDK_LOGGING,COMMONS_LOGGING,STDOUT_LOGGING,NO_LOGGING | Not set |
proxyFactory | 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 | CGLIB JAVASSIST | CGLIB |
vfslmpl | 指定 VFS 的实现 | 自定义 VFS 的实现的类全限定名,以逗号分隔。 | no set |
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) | true | false | true |
configurationFactory | 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3) | 类型别名或者全类名. | no set |
3.typeAliases
类型别名是为Java类型设置一个短的名字。它只和XML配置有关,存在的意义仅在于用来减少类完全限定名的冗余。例如:
在mabatis-config.xml配置
<!-- 设置别名-->
<typeAliases>
<typeAlias alias="aaa" type="com.aaa.entity.User"></typeAlias>
</typeAliases>
在mapper里那个userMapper.xml 里 resultType 写上别名
<select id="findAllUser" resultType="aaa">
select * from `user`
</select>
4.mybatis关联查询
4.1.多个条件查询
org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'ename' not found. Available parameters are [arg1, arg0, param1, param2] ### Cause: org.apache.ibatis.binding.BindingException: Parameter 'ename' not found. Available parameters are [arg1, arg0, param1, param2]
在使用多条件查询的时候,会出现上面的错误,以下有解决方案:
4.1.1 使用注解方式
/**
* 按照部门的名称和地址查询
#{}里面的名称对应的是注解@Param括号里面修饰的名称。
*/
List<User> findUserByNameAndAge(@Param("xxx") String name, @Param("yyy") String age);
<select id="findUserByNameAndAge" resultType="com.aaa.entity.User">
select * from user where name=#{xxx} and age=#{yyy}
</select>
4.1.2 使用内置变量arg0方式
/**
* 按照部门的名称和地址查询
*/
List<User> findUserByNameAndAge(String name,String age);
<select id="findUserByNameAndAge" resultType="com.aaa.entity.User">
select * from user where name=#{arg0} and age=#{arg1}
</select>
4.1.3 使用map封装多个参数(或者使用实体类)
/**
* 按照部门的名称和地址查询
Map #{}里面的名称对应的是Map里面的key名称
实体类 #{}里面的名称对应的是User类里面的成员属性
*/
List<Dept> findUserByNameAndAgeMap(Map map);
<select id="findUserByNameAndAgeMap" resultType="com.aaa.entity.User">
select * from dept where name=#{name} and age=#{age}
</select>
5.resultType和resultMap的区别
如果数据库中的列名和java实体类中属性名完全一致,可以使用resultType。
如果数据库的列名和实体类的属性名不一致,或者复杂查询产生临时列,新属性,临时列和新属性的名字不一致,resultmap就必须要使用了。
jdbcType 和JavaType的对应关系
JDBC Type Java Type CHAR String VARCHAR String LONGVARCHAR String NUMERIC java.math.BigDecimal DECIMAL java.math.BigDecimal BIT boolean BOOLEAN boolean TINYINT byte SMALLINT short INTEGER INTEGER BIGINT long REAL float FLOAT double DOUBLE double BINARY byte[] VARBINARY byte[] LONGVARBINARY byte[] DATE java.sql.Date TIME java.sql.Time TIMESTAMP java.sql.Timestamp CLOB Clob BLOB Blob ARRAY Array DISTINCT mapping of underlying type STRUCT Struct REF Ref DATALINK java.net.URL
5.1实体类属性名和表中字段名不一样 ,怎么办?
第1种:通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
<select id="getOrder" parameterType="int" resultType="com.jourwon.pojo.Order">
select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
</select>
第2种:通过resultMap 中的<result>来映射字段名和实体类属性名的一一对应的关系。
<select id="getOrder" parameterType="int" resultMap="orderResultMap">
select * from orders where order_id=#{id}
</select>
<resultMap type="com.jourwon.pojo.Order" id="orderResultMap">
<!–用id属性来映射主键字段–>
<id property="id" column="order_id">
<!–用result属性来映射非主键字段,property为实体类属性名,column为数据库表中的属性–>
<result property ="orderno" column ="order_no"/>
<result property="price" column="order_price" />
</reslutMap>
5.2 关联查询 多对一查询
多方为主表,一方为附表 查询所有员工的详细信息,包括部门名称,部门地址。
select e.*,d.dname,d.loc from emp e left join dept d on e.deptid = d.deptid
emp
private long empno;
private String ename;
private String job;
private long mgr;
private java.sql.Timestamp hiredate;
private double sal;
private double comm;
private Dept dept;
dept
private long deptid;
private String dname;
private String loc;
<?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.cloudwise.dao.EmpDao">
<resultMap id="empResultMap" type="com.cloudwise.entity.Emp" autoMapping="true">
<!-- 多对一-->
<association property="dept" javaType="com.cloudwise.entity.Dept" column="deptid" >
<id property="deptid" column="deptid"></id>
<result property="dname" column="dname"></result>
<result property="loc" column="loc"></result>
</association>
</resultMap>
<select id="findAllEmp" resultMap="empResultMap">
select e.*,d.dname,d.loc from emp e left join dept d on e.deptid = d.deptid
</select>
</mapper>
5.3 一对多查询
一个部门对应多个员工,需求,根据部门id查询单个部门的所有员工。
<!-- 一对多关系-->
<resultMap type="com.cloudwise.entity.Dept" id="DeptMap" autoMapping="true" >
<id property="deptno" column="deptno" jdbcType="INTEGER"></id>
<collection property="emps" ofType="com.cloudwise.entity.Emp" autoMapping="true" >
</collection>
</resultMap>
<!--查询单个-->
<select id="queryById" resultMap="DeptMap">
select d.* ,e.* from dept d left join emp e on d.deptno=e.deptno where d.deptno=#{deptno}
</select>
测试结果
[DEBUG][com.cloudwise.dao.DeptDao.queryById] - ==> Preparing: select d.* ,e.* from dept d left join emp e on d.deptno=e.deptno where d.deptno=?
[DEBUG][com.cloudwise.dao.DeptDao.queryById] - ==> Parameters: 10(Integer)
[DEBUG][com.cloudwise.dao.DeptDao.queryById] - <== Total: 3
Dept(deptno=10, dname=财务部, loc=郑州, emps=[Emp(empno=7782, ename=CLARK, job=MANAGER, mgr=7839, hiredate=Tue Jun 09 00:00:00 CST 1981, sal=2450.0, comm=null, deptno=10), Emp(empno=7839, ename=KING, job=PRESIDENT, mgr=null, hiredate=Tue Nov 17 00:00:00 CST 1981, sal=14764.0, comm=null, deptno=10), Emp(empno=7934, ename=MILLER, job=CLERK, mgr=7782, hiredate=Sat Jan 23 00:00:00 CST 1982, sal=1300.0, comm=null, deptno=10)])
部门人数3
5.4 mybatis多态sql
sql语句根据传入参数的状态动态改变,条件查询,批量删除
5.4.1 if满足一个条件自动拼接一条
select * from dept where 1=1
<if test="dname!=null and dname!=''">
and dname=#{dname}
</if>
<if test="loc!=null and loc!=''">
and loc=#{loc}
</if>
5.4.2 choose , when ,otherwise和Java 中的 switch case语句类似 只拼接一条
查询emp表,如果ename有值,按照enam查询,如果empno有值,按照empno查询
其他情况按照工资查询
<select id="findAllEmpByCondition" parameterType="com.cloudwise.entity.Emp" resultType="com.cloudwise.entity.Emp">
select * from emp
<where>
<choose>
<when test="ename!=null and ename!=''">
and ename =#{ename}
</when>
<when test="empno!=null and empno!=''">
and empno =#{empno}
</when>
<otherwise>
<if test="sal!=null and sal !=''">
and sal=#{sal}
</if>
</otherwise>
</choose>
</where>
</select>
5.4.3 where
<select id="findAllDeptByCondition" parameterType="dept" resultType="com.cloudwise.entity.Dept">
select * from dept
<where>
<if test="dname!=null and dname!=''">
and dname=#{dname}
</if>
<if test="loc!=null and loc!=''">
and loc=#{loc}
</if>
</where>
</select>
解决方案,在xml文件中尽量避免写大于,小于,大于等于,小于等于, &
5.4.4 使用数据库自动生成的主键
<!-- 使用数据库自动生成的主键-->
<insert id="addDept" keyProperty="deptno" useGeneratedKeys="true">
insert into dept ( dname, loc) values (#{dname},#{loc})
</insert>
@Test
public void addDept(){
Dept dept= new Dept();
dept.setDname("部门cc");
dept.setLoc("位置cc");
System.out.println("保存之前的dept:"+dept.toString());
int i = deptDao.addDept(dept);
System.out.println(i);
System.out.println("保存之后的dept:"+dept.toString());
}
5.4.5 #{} 和 ${}区别
#{}是占位符,预编译处理; ${}是拼接符,字符串替换,没有预编译处理。 Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。 #{} 可以有效的防止SQL注入,提高系统安全性;${}不能防止SQL注入 #{} 的变量替换是在DBMS中;${} 的变量替换是在DBMS外
5.4.6 模糊查询like语句
1 ’%${xxx}%’ 可能引起SQL注入,不推荐
2 "%"#{xxx}"%" 注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号, 所以这里 % 需要使用双引号,不能使用单引号 ,不然会查不到任何结果。
3 concat('%',#{xxx},'%') 使用CONCAT()函数,(推荐✨) 4 使用bind标签(不推荐)