MyBatis授课笔记

1 MyBatis概述

2 总体技术体系

①单一架构
一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one。
②分布式架构
一个项目,拆分成很多个模块,每个模块是一个工程。每一个工程都是运行在自己的Tomcat上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。

强调:SpringCloud离不开SpringBoot。每个微服务就是使用SpringBoot开发的,而各个微服务整体管理交给SpringCloud。

3 认识框架 framework

生活案例

MyBatis授课笔记1 - 图1
框架就是毛坯房,半成品。
SSM就是免费的毛坯房,大浪淘沙,Java领域中最流行的框架。
框架=框(限制 统一了风格)+架(支撑 保证质量、提高开发速度)

技术角度:

框架=jar包(特定问题的固定的解决方案)+配置文件(个性化,可变,比如数据库的四个连接参数、SQL语句、访问路径)
常用的基于JavaEE的三大开源框架,已经从SSH、SSH2过渡到了SSM:SpringMVC、Spring、MyBatis。
总之,框架是一个半成品,已经对基础的代码进行了封装并提供相应的API,开发者在使用框架是直接调用封装好的API可以省去很多代码编写,从而提高工作效率和开发速度。

4 认识MyBatis

MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。

MyBatis支持定制化SQL、存储过程以及高级映射
MyBatis避免了几乎所有的JDBC代码和手动设置参数以及结果集解析操作
MyBatis可以使用简单的XML或注解实现配置和原始映射;将接口和Java的POJO(Plain Ordinary Java Object,普通的Java对象)映射成数据库中的记录
Mybatis是一个半自动的ORM(Object Relation Mapping)框架

5 认识ORM

O:Object 对象
R:relational 关系
M:Mapping 映射
Java类——数据库表
Java类的成员变量—-数据库表的字段
Java类的对象——数据库表的记录
MyBatis授课笔记1 - 图2

6 MyBatis和Hibernate的对比

MyBatis授课笔记1 - 图3
而MyBatis将手写SQL语句的工作丢给开发者,可以更加精确的定义SQL,更加灵活,也便于优化性能。完成同样功能的两条SQL语句的性能可能相差十几倍到几十倍,在高并发、快响应要求下的互联网系统中,对性能的影响更明显。

总之,因为MyBatis具有封装少、映射多样化、支持存储过程、可以进行SQL语句优化等特点,符合互联网高并发、大数据、高性能、高响应的要求,使它取代Hibernate成为了Java互联网中首选的持久框架。而对于对性能要求不高的比如内部管理系统、ERP等可以使用Hibernate。

7 MyBatis入门(Hello World)

8 创建数据库表(物理建模)

CREATE DATABASE mybatis-example;
USE mybatis-example;
CREATE TABLE t_emp(
emp_id INT AUTO_INCREMENT,
emp_name CHAR(100),
emp_salary DOUBLE(10,5),
PRIMARY KEY(emp_id)
);
INSERT INTO t_emp(emp_name,emp_salary) VALUES(“tom”,200.33);
INSERT INTO t_emp(emp_name,emp_salary) VALUES(“jack”,300.33);
select * from t_emp

9 创建maven项目和实体类(逻辑建模)

使用MyBatis是否必须是Web项目?不是的。只要是可以使用JDBC的地方都可以使用MyBatis
使用Spring也不要求必须是Web项目
但是使用SpringMVC必须是Web项目,它对Servlet进行了封装。

@Data
@AllArgsConstructor
@NoArgsConstructorpublic class Employee { //t_emp
private Integer empId;//emp_id
private String empName;//emp_name
private Double empSalary;//emp_salary}

10 添加依赖(pom.xml)

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.3</version>
</dependency>
</dependencies>

11 创建配置文件(唯一的全局配置文件)

习惯上命名为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”/>
<property name=”url” value=”jdbc:mysql://localhost:3306/mybatis-example”/>
<property name=”username” value=”root”/>
<property name=”password” value=”root”/>
</dataSource>
</environment>
</environments>
<mappers>


</mappers>
</configuration>

12 创建映射文件(会有多个,主要内容就是SQL语句)

<?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=”a.b.c”>

<select id=”findAll” resultType=”com.atguigu.entity.Employee”>
select _ _from t_emp
</select>
</*mapper
>

13 代码开发

public class TestEmployee {
@Test
public void testFindAll() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
//SqlSessionFactory factory = new DefaultSqlSessionFactory();
InputStream is = Resources.getResourceAsStream(“mybatis-config.xml”);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//System.out.println(factory);
//2.使用SqlSessionFactory创建了SqlSession
SqlSession sqlSession = factory.openSession();
//3.使用SqlSession对数据库进行操作并得到结果
//仔细的体会一下:忽略了结果集处理的细节,直接返回List
List list = sqlSession.selectList(“a.b.c.findAll”);
//4.处理结果
list.forEach((emp)-> System.out.println(emp));
//关闭资源
sqlSession.close();
//factory.close();
}
}

14 运行测试

问题1:java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for a.b.c.findAll
原因1:写错了a.b.c.findAll namespace+id
原因2:在配置config()文件中没有指定映射(mapper)文件的位置

<mappers>

<mapper resource=”mappers/EmployeeMapper.xml”></mapper>
</mappers>

问题2:结果和记录条数相同,但是都是null
原因:数据库表和实体类已经映射,但是数据库表的字段和实体类的成员变量没有映射,名称不同,还不能自动映射。

<select id=”findAll” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
</select>

问题3:出现了JDK版本的不匹配。提示1.5和1.8不匹配
原因:Maven项目创建的默认JDK版本是1.5
解决方案1:修改idea的多个配置
MyBatis授课笔记1 - 图4

MyBatis授课笔记1 - 图5

MyBatis授课笔记1 - 图6

解决2:给Maven指定全局配置或者项目的局部配置

<properties>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<maven.compiler.source>8</maven.compiler.source>

<maven.compiler.target>8</maven.compiler.target>
</properties>

问题4:我感觉MyBatis也不简单
这是在搭建项目框架,很多操作只需要在此处做一次。搭建好框架后,其实的操作就是:
在映射文件中写sql语句和在测试类调用sql语句。
之前在JDBC中SQL语句写在代码中,现在SQL语句写在映射文件中,便于修改。

//仔细的体会一下:忽略了结果集处理的细节,直接返回List List list = sqlSession.selectList(“a.b.c.findAll”);

15 总结和底层细节

MyBatis授课笔记1 - 图7

误区:SQL文件在xml文件,如果每次要执行该SQL语句时都去读取xml文件,效率太低。
解释:创建SqlSessionFactory时,会将配置文件和映射文件的信息都读到内存中,按照特定的结构来存在,不会每次都读硬盘。

MyBatis授课笔记1 - 图8

MyBatis授课笔记1 - 图9

16 MyBatis入门完善(Hello World)

17 添加日志log4j

  1. 添加依赖 | <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency> | | —- |
  1. 准备配置文件,比如在resources目录下,名称固定log4j.xml | <?xml version=”1.0” encoding=”UTF-8” ?><!DOCTYPE log4j:configuration SYSTEM “log4j.dtd”>
    <log4j:configuration xmlns:log4j=”http://jakarta.apache.org/log4j/>
    <appender name=”STDOUT” class=”org.apache.log4j.ConsoleAppender”>
    <param name=”Encoding” value=”UTF-8” />
    <layout class=”org.apache.log4j.PatternLayout”>
    <param name=”ConversionPattern” value=”%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n” />
    </layout>
    </appender>
    <logger name=”org.apache.ibatis”>
    <level value=”info” />
    </logger>
    <root>
    <level value=”debug” />
    <appender-ref ref=”STDOUT” />
    </root>
    </log4j:configuration> | | —- |
  1. 观察效果(出现了SQL语句的DEBUG日志)

MyBatis授课笔记1 - 图10

  1. 理解配置文件
    1. Appender:目的地 日志写到哪里 ConsoleAppender 控制台 org.apache.log4j.FileAppender 写到文件
    2. Layout:格式 日志写出什么样子 PatternLayout 指定格式 SimpleLayout HTMLLayout
    3. Level:日志级别 FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)

log4j曾经是最流行的日志框架,但是已经不再维护;现在建议使用log4j2和logback。

18 提取连接参数属性文件

  1. 提取属性文件。要写在resources目录下,名称任意 | wechat.dev.driver=com.mysql.jdbc.Driverwechat.dev.url=jdbc:mysql://localhost:3306/mybatis-examplewechat.dev.username=rootwechat.dev.password=root | | —- |
  1. 在配置文件中引入并使用属性文件
<properties resource=”jdbc.properties”></properties><environments default=”development”>

<environment id=”development”>

<transactionManager type=”JDBC”/>

<dataSource type=”POOLED”>
<property name=”driver” value=”${wechat.dev.driver}”/>
<property name=”url” value=”${wechat.dev.url}”/>
<property name=”username” value=”${wechat.dev.username}”/>
<property name=”password” value=”${wechat.dev.password}”/>
</dataSource>
</environment>
</environments>

19 MyBatis单表开发1-无接口方式

20 findById

<select id=”findById” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId}
</select>
@Test
public void testFindById() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
InputStream is = Resources.getResourceAsStream(“mybatis-config.xml”);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//2.使用SqlSessionFactory创建了SqlSession
SqlSession sqlSession = factory.openSession();
//3.使用SqlSession对数据库进行操作并得到结果
//仔细的体会一下:忽略了结果集处理的细节,直接返回List
Employee emp = sqlSession.selectOne(“a.b.c.findById”,2);
//4.处理结果
System.out.println(emp);
//关闭资源
sqlSession.close();
}
注意事项:
1. 映射文件使用#{}来接收参数,参数名任意,但是建议见名知义
1. 测试类中调用selectOne(),需要指定具体的参数

21 save


<insert id=”insertEmp”>
insert into t_emp values(null,#{empName},#{empSalary})
</insert>
@Test
public void testInsertEmp() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
InputStream is = Resources.getResourceAsStream(“mybatis-config.xml”);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//2.使用SqlSessionFactory创建了SqlSession
//方式1:自动提交事务,每个DML操作时一个单独的事务
//SqlSession sqlSession = factory.openSession(true);
//方式2:手动的提交事务
SqlSession sqlSession = factory.openSession();//默认false
//3.使用SqlSession对数据库进行操作并得到结果
Employee emp = new Employee(null,“John”,400.0);
int n = sqlSession.insert(“a.b.c.insertEmp”,emp);
//手动提交或者回滚事务
sqlSession.commit();
//sqlSession.rollback();
//4.处理结果
System.out.println(n);
//关闭资源
sqlSession.close();
}
注意事项:DML操作需要提交事务
//方式1:自动提交事务,每个DML操作时一个单独的事务
SqlSession sqlSession = factory.openSession(true);
方式2:手动的提交事务
sqlSession.commit();

问题:There is no getter for property named ‘empName1’ in ‘class com.atguigu.entity.Employee’
insert into t_emp values(null,#{empName},#{empSalary})
底层调用的参数名称的getter方法 empName,其实调用的是getEmpName(),使用反射技术
如果没有getter方法,就会使用使用反射直接操作同名的成员变量

22 update

<update id=”updateEmp”>
update t_emp set emp_name=#{empName},emp_salary=#{empSalary} where emp_id =#{empId}
</update>
@Testpublic void testUpdateEmp() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
InputStream is = Resources.getResourceAsStream(“mybatis-config.xml”);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//2.使用SqlSessionFactory创建了SqlSession
//方式1:自动提交事务,每个DML操作时一个单独的事务
//SqlSession sqlSession = factory.openSession(true);
//方式2:手动的提交事务
SqlSession sqlSession = factory.openSession();//默认false
//3.使用SqlSession对数据库进行操作并得到结果
Employee emp = new Employee(6,“Johnson”,500.0);
int n = sqlSession.update(“a.b.c.updateEmp”,emp);
//手动提交或者回滚事务
sqlSession.commit();
//sqlSession.rollback();
//4.处理结果
System.out.println(n);
//关闭资源
sqlSession.close();
}

23 delete




<insert id=”deleteEmp”>
delete from t_emp where emp_id = #{abc}
</insert>
@Testpublic void testDeleteEmp() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
InputStream is = Resources.getResourceAsStream(“mybatis-config.xml”);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//2.使用SqlSessionFactory创建了SqlSession
//方式1:自动提交事务,每个DML操作时一个单独的事务
//SqlSession sqlSession = factory.openSession(true);
//方式2:手动的提交事务
SqlSession sqlSession = factory.openSession();//默认false
//3.使用SqlSession对数据库进行操作并得到结果
//int n = sqlSession.delete(“a.b.c.deleteEmp”,6);
int n = sqlSession.update(“a.b.c.deleteEmp”,6);
//手动提交或者回滚事务
sqlSession.commit();
//sqlSession.rollback();
//4.处理结果
System.out.println(n);
//关闭资源
sqlSession.close();
}

24 更多细节

  1. 映射文件中使用delete、update、insert标签没有区别,代码中调用SqlSession的insert、update、delete方法没有区别,关键是SQL语句
  2. 因为不管调用insert、update、delete哪个方法,底层调用的都是update(),更底层都是调用JDBC的executeUpdate()
  3. 不管查询是调用的是selectList(),还是selectOne(),底层调用的都是selectList.
  4. 目前已经学习了SqlSession的哪些方法
    1. selectList
    2. selectOne
    3. insert
    4. update
    5. delete
    6. Commit
  5. MyBatis的常用API
    1. SqlSessionFactory
    2. SqlSession: 它是Connection,还是Statement呢?都不是!!!!

      25 MyBatis单表开发2-Mapper代理接口方式

      前面已经使用MyBatis完成了对Emp表的CRUD操作,都是由SqlSession调用自身方法发送SQL命令并得到结果的,实现了MyBatis的入门。 但是却存在如下缺点: 1).不管是selectList()、selectOne()、selectMap(),都只能提供一个查询参数。如果要多个参数,需要封装到JavaBean中,并不一定永远是一个好办法。 2).返回值类型较为固定 3).只提供映射文件,没有提供数据库操作的接口,不利于后期的维护扩展,也和之前开发习惯不符
      在MyBatis中提供了另外一种成为Mapper代理(或称为接口绑定)的操作方式。在实际开发中也使用该方式。
      Mybatis中的Mapper接口相当于以前的Dao。但是区别在于,Mapper仅仅是接口,我们不需要提供实现类

26 定义接口 相当于使用JDBC时的DAO接口,但是不需要写实现类

| public interface EmployeeMapper {
public List findAll();
public Employee findById(Integer empId);
public int saveEmp(Employee emp);
public int updateEmp(Employee emp);
public int deleteEmp(Integer empId);

  1. **public **List<Employee> findEmp(String ename,String salary);<br />} |

| —- |

27 映射文件

<mapper namespace=”com.atguigu.mapper.EmployeeMapper”>

<select id=”findAll” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
</select>
<insert id=”saveEmp”>
insert into t_emp values(default,#{empName},#{empSalary})
</insert>
</mapper>

对命名空间namespace有要求:必须是Mapper接口的完整路径名称-
对具体操作的ID有要求:必须是接口的方法名
异常:org.apache.ibatis.binding.BindingException: Type interface com.atguigu.mapper.EmployeeMapper is not known to the MapperRegistry.
异常:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.atguigu.mapper.EmployeeMapper.findAll

28 测试

public class TestEmployee {
SqlSession sqlSession = null;
@Before
public void before() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
InputStream is = Resources.getResourceAsStream(“mybatis-config.xml”);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//2.使用SqlSessionFactory创建了SqlSession
sqlSession = factory.openSession();//默认false
}
@After
public void after(){
//手动提交或者回滚事务
sqlSession.commit();
//关闭资源
sqlSession.close();
}
@Test
public void testFindAll(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List list = mapper.findAll();
list.forEach((emp)-> System.out.println(emp));
}
@Test
public void testInsertEmp(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = new Employee(null,“bill gates”,500.0);
int n = mapper.saveEmp(emp);
System.out.println(n);
}
}

注意1: 因为MyBatis的CRUD操作流程固定,含有共同的代码,可以提取,并添加@Before、@After
注意2使用该方式,第一步要通过SqlSession的getMapper()获取指定接口的动态生成的实现类对象

底层原理:底层使用了动态代理的设计模式实现了MyBatis的Mapper代理(接口绑定)

看底层确定是动态代理
MyBatis授课笔记1 - 图11
看结果,确定是动态代理
MyBatis授课笔记1 - 图12

29 给SQL语句传参的两种方式

30 使用#{} 底层使用PreparedStatement

<select id=”findById” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_id = #{empId}
</select>

MyBatis授课笔记1 - 图13

31 使用${} 底层使用Statement

<select id=”findById2” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_id = ${empId}
</select>

MyBatis授课笔记1 - 图14

32 使用${}和#{}实现模糊查询

<select id=”findEmp” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like concat(“%”,#{ename},”%”)
</select>

MyBatis授课笔记1 - 图15

<select id=”findEmp2” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like ‘%${ename}%’
</select>

MyBatis授课笔记1 - 图16

结论:推荐使用#{},安全,不需要拼接字符串

33 数据输入

讲解的是方法的参数可以是一个还是多个,是基本类型还是复杂类型,在SQL语句中应该如何接收。肯定是使用#{}接收,但是具体如何来接收。

给实体类起别名,方便resultType使用
方法1:给每一个类起别名,别名任意

<typeAliases>
<typeAlias type=”com.atguigu.entity.Employee” alias=”employee”></typeAlias>
<typeAlias type=”com.atguigu.entity.Dept” alias=”dept”></typeAlias>
</typeAliases>

方法2:给一个包下的所有类起别名,别名是类名的首字母小写,其他字母不变

<typeAliases> <package name=”com.atguigu.entity”/>
</typeAliases>

34 单个简单参数(Integer、Double、String)

public Employee findById(Integer empId);public List findEmp(String empName);
<select id=”findById” resultType=”employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_id = #{empId}
</select>

<select id=”findEmp” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like “%”#{ename}”%”
</select> | | 使用#{}来接收,参数名任意,建议见名知义。
经过测试发现对于模糊查询,#{}也可以使用 “%”#{ename}”%”,要求是双引号。 |

35 单个引用参数(Employee)

public int saveEmp(Employee emp);
<insert id=”saveEmp”>
insert into t_emp values(default,#{empName},#{empSalary})
</insert>
底层调用的参数名称的getter方法 empName,其实调用的是getEmpName(),使用反射技术
如果没有getter方法,就会使用使用反射直接操作同名的成员变量

36 单个引用参数(Map)

public List findEmp2(Map map);
<select id=”findEmp2” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like concat(“%”,#{ename11},”%”) and emp_salary>=#{minSalary}
</select>
@Testpublic void testFindEmp2(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Map map = new HashMap<>();
map.put(“ename11”,“oh”);
map.put(“minSalary”,400);
List list = mapper.findEmp2(map);
list.forEach((emp)-> System.out.println(emp));
}
#{key}使用Map的key来接收该key对应的value

37 多个简单参数(Integer、Double、String)

public List findEmp3(String ename,Double minSalary);public List findEmp4(@Param(“ename”) String ename,@Param(“minSalary”) Double minSalary);
<select id=”findEmp3” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like concat(“%”,#{param1},”%”) and emp_salary>=#{param2}
</select>

<select id=”findEmp4” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like concat(“%”,#{ename},”%”) and emp_salary>=#{minSalary}
</select> | | Parameter ‘ename’ not found. Available parameters are [arg1, arg0, param1, param2] |

推荐使用@Param方式,增加可读性。

38 多个引用参数(Employee,Dept)

public int saveEmp2(Employee emp, Dept dept);
public int saveEmp3(@Param(“emp”)Employee emp, @Param(“dept”) Dept dept);
<insert id=”saveEmp2”>
insert into t_emp values(default,#{param1.empName},#{param1.empSalary},#{param2.deptno})
</insert>

<insert id=”saveEmp3”>
insert into t_emp values(default,#{emp.empName},#{emp.empSalary},#{dept.deptno})
</insert> |

39 到底该使用哪个

如果有多个参数
参数数量不多(3,4个) 建议使用
public List findEmp4(@Param(“ename”) String ename,@Param(“minSalary”) Double minSalary);
参数数量多(大于4个),将多个参数封装到一个实体类中
public int saveEmp(Employee emp);