应用框架@MyBatis

1 前言:数据库 SQL命令

  • 创建数据库并指定编码
Create database 数据库名 default character set utf8
  • 创建表
Createtable 表名(
   列名 类型 约束 auto_increament comment "备注"
);

2 命名规范

  • 项目名:没有要求,不起中文
  • 包:公司域名倒写 com
  • 数据访问层:dao, persist, mapper
  • 实体:entity, model, bean,javabean, pojo
  • 业 务 逻 辑 : service,biz
  • 控制器: controller, servlet,action,web
  • 过滤器: filter
  • 异常: exception
  • 监听器:listener
  • 注释:
    • 类上和方法上使用文档注释 /** */
    • 在方法里面使用/* *///
  • 类: 大驼峰
  • 方法,属性:小驼峰

3 MVC开发模式

  • M: Model 模型;实体类和业务和 dao

  • V: view 视图 ;现在一般使用. JSP

  • C: Controller 控制器,现在使用servlet

    • 作用:视图和逻辑分离
  • MVC 适用场景:大型项目开发.

  • 项目实现步骤

    • 先设计数据库

    • 再写实体类:用于封装数据

    • 持久层DAO

  • 业务逻辑

    • 控制器

    • 视图

      MVC开发模式

4 框架概念

  • 框架:软件的半成品.未解决问题制定的一套约束,在提供功能基础上进行扩充。
  • 框架中一些不能被封装的代码(变量),需要使用框架者新建一个 xml 文件,在文件中添加变量内容
    • 需要建立特定位置和特定名称的配置文件
    • 需要使用 xml 解析技术和反射技术
    • 是 MySQL Mapper Framework for Java

常用概念

  • 类库:提供的类没有封装一定逻辑。
    举例:类库就是名言警句,写作文时引入名言警句

  • 框架:区别于类库,里面有一些约束。
    举例:框架是填空题

5 MyBatis 简介

  • Mybatis 开源免费框架。原名叫 iBatis,2010 在 google code,2013 年迁移到 github

  • 作用: 数据访问层(即是 Dao)框架.

    • 底层是对 JDBC 的封装.
  • mybatis 优点之一:

    • 使用 mybatis 时不需要编写实现类,只需要写需要执行的 sql 命令。

6 MyBatis 使用

(一)环境搭建

  • 步骤一:导入 jar
包名 作用
mybatis.jar MyBatis 的核心包
mysql-connector-java.jar MySQL 驱动包
cglib.jar 动态代理包
asm.jar cglib 依赖包
javassist-GA.jar cglib 依赖包(负责字节码解析的包)
commons-logging.jar 日志包
log4j.jar 日志包
log4j-api.jar 日志包
log4j-core.jar 日志包
slf4j-api.jar 日志包
slf4j-log4j.jar 日志包
  • 步骤二:在 src 下新建全局配置文件(编写 JDBC 四个变量)
    • 没有名称和地址要求
    • 在全局配置文件中引入 DTD 或 schema【使用见 Blogs -> DTD】
      • 如果导入 dtd 后没有提示:Window--> preference --> XML --> XMl catalog --> add 按钮
    • 全局配置文件内容【MyBatis.xml】
<?xml version="1.0" encoding="UTF-8"?>
<!--注意这里是 configuration ,下面为 mybatis-3-config -->
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
  
<configuration>
  <!-- default 为引用 environment 的 id,即当前所使用的环境 -->
  <environments default="default">
  	<!-- 声明可以使用的环境 -->
  	<environment id="default">
  		<!-- 使用原生 JDBC 事务 -->
  		<transactionManager type="JDBC"></transactionManager>
  		<dataSource type="POOLED">
      		  <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
      	          <property name="url" value="jdbc:mysql://localhost:3306/数据库名"/>
      		  <property name="username" value="root"/>
      	          <property name="password" value="GJXAIOU"/>
  		</dataSource>
  	</environment>
  </environments>
  
  <mappers>
      <!-- 配置实体类 Mapper 文件,一般使用 package -->
  	<mapper resource="com/mapper/FlowerMapper.xml"/>
  </mappers>
</configuration>

注: mybatis.xml 中的 <mappers>中配置的 resource 是整个项目中的所有其他 实体类Mapper.xml 文件;

mappers 标签下有许多 mapper 标签,每一个 mapper 标签中配置的都是一个独立的映射配置文件的路径,配置方式有以下几种。

第一种:使用相对路径进行配置。示例代码如下:

<mappers>
    <mapper resource="org/mybatis/mappers/UserMapper.xml"/>
    <mapper resource="org/mybatis/mappers/ProductMapper.xml"/>
    <mapper resource="org/mybatis/mappers/ManagerMapper.xml"/>
</mappers>

第二种:使用绝对路径进行配置。示例代码如下:

<mappers>
    <mapper url="file:///var/mappers/UserMapper.xml"/>
    <mapper url="file:///var/mappers/ProductMapper.xml"/>
    <mapper url="file:///var/mappers/ManagerMapper.xml"/>
</mappers>

第三种:使用接口信息进行配置。示例代码如下:

<mappers>
    <mapper class="org.mybatis.mappers.UserMapper"/>
    <mapper class="org.mybatis.mappers.ProductMapper"/>
    <mapper class="org.mybatis.mappers.ManagerMapper"/>
</mappers>

第四种:使用接口所在包进行配置。示例如下:

<mappers>
    <package name="org.mybatis.mappers"/>
</mappers>
  • 步骤三:新建以 mapper 结尾的包,在包下新建:实体类名+Mapper.xml
    • 文件作用:编写需要执行的 SQL 命令
    • 把 xml 文件理解成实现类

FlowerMapper.xml 文件内容为:【注意抬头中的信息不同,将上面抬头中的 config 全部换为 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">
<!-- namespace:理解成实现类的全路径(包名+类名),用于绑定 Dao 接口(即面向接口编程),使用 namespace 之后就不用写实现类,业务逻辑会直接通过这个绑定寻找到对应点的 SQL 语句进行对应的数据处理 -->
<mapper namespace="a.b" >
	<!-- 如果方法返回值是list,在resultType中写List的泛型,因为mybatis
		对jdbc封装,一行一行读取数据-->
	<!--id:表示方法名; parameterType:定义参数类型;resultType:返回值类型 -->
	<select id="selAll" resultType="com.pojo.Flower">
		select * from flower
	</select>
</mapper>
  • 步骤四:测试结果(只有在单独使用 mybatis 时使用,最后 ssm 整合时下面代码不需要编写
import com.pojo.Flower;

public class Test {
	public static void main(String[] args) throws IOException {
		InputStream is = Resources.getResourceAsStream("mybatis.xml");
		//使用工厂设计模式
		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
		//生产SqlSession
		SqlSession session=factory.openSession();
		
		List<Flower> list = session.selectList("a.b.selAll");
		for (Flower flower : list) {
			System.out.println(flower.toString());
		}
		
		session.close();
	}
}

(二)环境搭建详解

全局配置文件中内容

  • <transactionManager/> type 属性可取值如下:
    • JDBC,事务管理使用 JDBC 原生事务管理方式;
    • MANAGED 把事务管理转交给其他容器,相当于设置原生 JDBC 事务setAutoMapping(false)
  • <dataSouce/>type 属性可取值如下:
    • POOLED:使用数据库连接池;
    • UNPOOLED:不使用数据库连接池,和直接使用 JDBC 一样;
    • JNDI:是 java 命名目录接口技术;

7 数据库连接池【补充知识】

  • 在内存中开辟一块空间,存放多个数据库连接对象;
  • JDBC Tomcat Pool,直接由 tomcat 产生数据库连接池;
  • 图示:数据库连接池中有很多连接,他们可能处于 Active 或者 Idle 等等状态;
    • active 活跃状态:当前连接对象被应用程序使用中
    • Idle 空闲状态:等待应用程序使用
  • 使用数据库连接池的目的
    • 在高频率访问数据库时,使用数据库连接池可以降低服务器系统压力,提升程序运行效率;
      • 小型项目不适用数据库连接池;
  • 实现 JDBC tomcat Pool 的步骤
    • 在 web 项目的 META-INF 中存放 context.xml,在 context.xml 编写数据库连接池相关属性
    • 把项目发布到 tomcat 中,则数据库连接池产生了
<?xml version="1.0" encoding="UTF-8"?>
<Context>
	<Resource
		driverClassName="com.mysql.jdbc.Driver"
		url="jdbc:mysql://localhost:3306/lianxi"
		username="root"
		password="moyue"
		maxActive="50"
		maxIdle="20"
		name="test"
		auth="Container"
		maxWait="10000"
		type="javax.sql.DataSource"
	/>
</Context>
  • 可以在 Java 中使用 JNDI 获取数据库连接池中对象
  • Context:上下文接口.context.xml 文件对象类型;代码见下面
  • 当关闭连接对象时,把连接对象归还给数据库连接池,把状态改变成 Idle
@WebServlet("/pool")
public class DemoServlet extends HttpServlet {
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
  	  try {
  		Context cxt = new InitialContext();
  		DataSource ds = (DataSource) cxt.lookup("java:comp/env/test");
  		Connection conn = ds.getConnection();
  		PreparedStatement ps = conn.prepareStatement("select * from flower");
  		ResultSet rs = ps.executeQuery();
  		res.setContentType("text/html;charset=utf-8");
  		PrintWriter out = res.getWriter();
  		while(rs.next()){
  			out.print(rs.getInt(1)+"&nbsp;&nbsp;&nbsp;&nbsp;"+rs.getString(2)+"<br/>");
  		}
  		out.flush();
  		out.close();
  		rs.close();
  	} catch (NamingException e) {
  		e.printStackTrace();
  	} catch (SQLException e) {
  		e.printStackTrace();
  	}
  }
}

8 三种查询方式

  • selectList() 返回值为 List<resultType 属性控制>
    适用于查询结果都需要遍历的需求

  • selectOne() 返回值 Object
    适用于返回结果只是变量或一行数据时

  • selectMap() 返回值 Map<key,resultType 属性控制>
    适用于需要在查询结果中通过某列的值取到这行数据的需求

public class Test {
	public static void main(String[] args) throws IOException {
        /** MyBatis 默认不加载配置文件,因此需要先加载配置文件,返回整个配置文件的流对象;
        * 在数据访问层处理异常和在控制器中处理异常,一般在 service 中只抛出异常;
        */
		InputStream is = Resources.getResourceAsStream("mybatis.xml");
		// 使用工厂设计模式
		// 前面是工厂  实例化工厂对象时使用的是构建者设计模式   它的名称标志:后面有Builder 
		// 构建者设计模式意义: 简化对象实例化过程
		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
		// 生产 SqlSession, 整个 sqlsession 就是 MyBatis 中 API 封装的对象,增删改查都在里面
		SqlSession session=factory.openSession();
		
		// selectList
		List<Flower> list = session.selectList("a.b.selAll");
		for (Flower flower : list) {
			System.out.println(flower.toString());
		}
		
		// selectOne
		int count = session.selectOne("a.b.selById");
		System.out.println(count);
		
		// selectMap
		// 把数据库中哪个列的值当作 map 的 key
		Map<Object, Object> map = session.selectMap("a.b.c", "name123");
		System.out.println(map);
		
		session.close();
	}
}

对应的 实体类 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="a.b" >
	<select id="selAll" resultType="com.pojo.Flower">
		select id,name name123,price,production from flower
	</select>
	
	// 这里的 int 相当于 Ingeter
	<select id="selById" resultType="int">
		select count(*) from flower
	</select>
	
	<select id="c" resultType="com.pojo.Flower">
		select id,name name123,price,production from flower
	</select>
	
</mapper>

9 注解

  • 注解存在的意义:简化 xml 文件的开发;
  • 注解在 servlet 3.0 规范之后大力推广的;
  • 注解前面的@XXX,表示引用一个 @interface;
    • @interface 表示注解声明
  • 注解可以有属性,因为注解其实就是一个接口(类);
  • 每次使用注解都需要导包
  • 注解语法: @XXXX(属性名= 值)
  • 值的分类
    • 如果值是基本数据类型或字符串: 属性名=值
    • 如果值是数组类型: 属性名={值,值}
      • 如果只有一个值可以省略大括号;
    • 如果值是类类型:属性名=@名称
  • 如果注解只需要给一个属性赋值,且这个属性是默认属性,可以省略属性名

10 路径

  • 编写路径为了告诉编译器如何找到其他资源;

  • 路径分类

    • 相对路径: 从当前资源出发找到其他资源的过程
    • 绝对路径: 从根目录(服务器根目录或项目根目录)出发找到其他资源的过程
    • 标志: 只要以/开头的都是绝对路径
  • 绝对路径:
    • 如果是请求转发 / 表示项目根目录(WebContent)
    • 其他重定向,<img/> <script/><style/>,location.href 等/都表示服务器根目录(tomcat/webapps 文件夹)
  • 如果客户端请求的控制器,控制器转发到JSP 后,jsp 中如果使用相对路径,需要按照控制器的路径去找其他资源;
    • 保险办法:使用绝对路径,可以防止上面的问题;

11 Log4J

  • 由 apache 推出的开源免费日志处理的类库;

  • 为什么需要日志:

    • 在项目中编写 System.out.println();输出到控制台,当项目发布到 tomcat 后,没有控制台(在命令行界面能看见.),不容易观察一些输出结果。
    • log4j 作用:不仅能把内容输出到控制台,还能把内容输出到文件中,便于观察结果。
  • 使用步骤:

    • 导入 log4j-xxx.jar
    • 在 src 下新建 log4j.properties(路径和名称都不允许改变)
      • 在 ConversionPattern 中书写表达式(就是日志输出的格式);
      • log4j.appender.LOGFILE.File 日志文件位置及名称(日志文件扩展名.log)
// 遇到什么级别才输出以及输出位置:调试信息输出, 输出到控制台,输出到 log 文件
log4j.rootCategory=DEBUG, CONSOLE, LOGFILE
// 向控制台输出相关的配置
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%C %d{YYYY-MM-dd hh:mm:ss}%m  %n
// 向日志文件输出的相关的配置
log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=E:/my.log 
log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.Patter nLayout log4j.appender.LOGFILE.layout.ConversionPattern=%C	%m %L %n
  • log4j 输出级别(一共五个级别)

    • fatal(致命错误) > error ( 错误) > warn ( 警告) > info(普通信息) > debug(调试信息)
    • 在 log4j.properties 的第一行中控制输出级别,只有大于等于该级别信息才输出
  • log4j 输出目的地

    • 在一行控制输出目的地,其中 CONSOLE 表示控制台 ,LOGFILE 表示文件

在实际使用:新建一个类,例如:Test类

  • 使用方法一:
Logger  logger = Logger.getLogger(Test.class);
// 这里以 debug 为例,具体的方法看上面输出级别调用不同的方法
logger.debug("这是调试信息"); 
  • 使用方法二:
    在 catch 中 : logger.error(e.getMessage()); 即可,注意在log4j配置文件中加上相关的表达式;

  • pattern 中常用几个表达式(区分大小写)

    • %C 输出包名+类名;
    • %d{YYYY-MM-dd HH:mm:ss} 时间;
    • %L 行号
    • %m 信息
    • %n 换行
      上面可以多个一起使用, 中间空格就行
      示例如下:

pattern示例配置

12 <settings>标签

该标签必须配置在最前面

  • 在 mybatis 全局配置文件中通过<settings>标签控制 mybatis 全局开关
  • 在 mybatis.xml 中开启 log4j 命令如下:
    • 必须保证有 log4j.jar
    • 在 src 下有 log4j.properties
<settings>
    <setting  name="logImpl"  value="LOG4J"/>
</settings>
  • log4j 中可以输出指定内容的日志(控制某个局部内容的日志级别) :直接在 log4j.properties 中设置;
    • 命名级别(包级别): <mapper>namespace 属性中除了最后一个类名
      例如 namespace=”com.mapper.PeopleMapper” 其中包级别为 com.mapper ,需要在 log4j.propeties 中做两件事情
    • 先在总体级别调成 Error,这样可以不输出无用信息
    • 在设置某个指定位置级别为 DEBUG

根据命名空间设置不同优先级

  • 类级别
    • namespace 属性值 ,相当于namespace 类名
  • 方法级别
    • 使用 namespace 属性值+标签 id 属性值

13 parameterType 属性(Mybatis 中参数设置)

传递多个参数时候,可以使用对象或者 map,和第二部分的多参数实现方法

  • 在 XXXMapper.xml 中<select> <delete>等标签的 parameterType 可以控制参数类型(例如可以传入 select 语句的参数,控制输入参数的类型)

  • SqlSession 的 selectList()selectOne() 的第二个参数和 selectMap() 的第三个参数都表示方法的参数

    • 示例:
People  p  =  session.selectOne("a.b.selById",1);
System.out.println(p);
  • 在 实例名Mapper.xml 中可以通过#{}获取参数(代码见下)
    • parameterType 控制参数类型
    • #{} 获取参数内容
      • 使用索引,从 0 开始 #{0}表示第一个参数(尽量不使用这个方法)
      • 也可以使用#{param1},表示第一个参数
      • 如果只有一个参数(其参数应该是基本数据类型或 String),mybatis 对 #{} 里面内容没有要求只要写内容即可。
      • 如果参数是对象#{属性名}
      • 如果参数是 map, 写成#{key} :当需要传递多个参数时候,目前只能使用 map 或者对象
<select id="selById"
  resultType="com.pojo.People"  parameterType="int">
  select * from people where id=#{0}
</select>
  • #{}${} 的 区 别
    • #{} 获取参数的内容,支持索引获取,或者使用 param1 获取指定位置参数,并且 SQL 使用?占位符
    • ${} 字符串拼接,不使用?,默认找${内容}内容的 get/set 方法,如果写数字,就是一个数字

配置示例:
PeopleMapper.xml

<select id="test" resultType="com.pojo.People" parameterType="com.pojo.People"> 
  select * from people where id =  ${id}
</select>

Test.java

People peo =new People();
peo.setId(1);
People p = session.selectOne("a.b.selById",people);
  • 如果在 xml 文件中出现 “<” , “>” ,双引号 等特殊字符时可以使用 XML 文件转义标签(XML 自身的),格式为:<![CDATA[ 内 容 ]]>

  • mybatis 中实现 mysql 分页写法

  • ? 中不允许在关键字前后进行数学运算,需要在代码中计算完成后传递到 mapper.xml 中;

Java 中代码为:

//显示几个
int pageSize = 2;
//第几页
int pageNumber = 2;
//如果希望传递多个参数,可以使用对象或map
Map<String,Object> map = new HashMap<>();
map.put("pageSize", pageSize);
map.put("pageStart", pageSize*(pageNumber-1));
List<People> p = session.selectList("a.b.page",map);

mapper.xml 中代码为:

<select id="page" resultType="com.pojo.People" parameterType="map">
    select * from people limit #{pageStart},#{pageSize}
</select>

14 typeAliases 别名(在mybatis.xml中进行配置,命令如下)

别名配置必须在 <environments>前面
一共有三类:分别是系统内置的别名,给某个类的别名,

  • 系统内置别名: 就是把类型全小写(见文档 )
  • 给某个类起别名
    • alias=”自定义”
    • 同时 mapper.xml 中 peo 引用 People 类
<typeAliases>
    <typeAlias type="com.pojo.People" alias="peo"/>
</typeAliases>

mapper.xml 中内容为:

<select id="page" resultType="peo" parameterType="map">
    select * from people limit #{pageStart},#{pageSize}
</select>
  • 直接给某个包下所有类起别名,别名为类名,区分大小写
    • mybatis.xml 中配置
<typeAliases>
    <package name="com.pojo" />
</typeAliases>
  • mapper.xml 中通过类名引用
<select id="page" resultType="People" parameterType="map">
    select * from people limit #{pageStart},#{pageSize}
</select>

15 MyBatis 实现新增

  • 概念复习:下面三者本质相同,角度不同;

    • 功能:从应用程序角度出发,软件具有哪些功能.
    • 业务:完成功能时的逻辑;对应 Service 中一个方法
    • 事务:从数据库角度出发,完成业务时需要执行的 SQL 集合,统称一个事务
      • 事务回滚:如果在一个事务中某个 SQL 执行事务,希望回归到事务的原点,保证数据库数据的完整性
  • 在 mybatis 中默认是关闭了 JDBC 的自动提交功能

    • 每一个 SqlSession 默认都是不自动提交事务.
    • 可以使用 session.commit() 提交事务.
    • 也可以使用 openSession(true); 自动提交底层为:.setAutoCommit(true);
  • mybatis 底层是对 JDBC 的封装.

    • JDBC 中 executeUpdate()执行新增,删除,修改的 SQL,方法的返回值 int,表示受影响的行数.
    • 应为上面原因,因此 mybatis 中 <insert> <delete> <update> 标签没有 resultType 属性,认为返回值都是 int
  • 在 openSession() 时 Mybatis 会创建 SqlSession 时同时创建一个 Transaction(事务对象),同时 autoCommit 都为 false

    • 如果出现异常,应该 session.rollback() 回滚事务.
  • 实现新增的步骤

    • 在 mapper.xml 中提供 <insert> 标签,标签没有返回值类型
    • 通过 session.insert() 调用新增方法

mapper.xml 值为;

<insert id="ins" parameterType="People">
insert into people values(default,#{name},#{age})
</insert>
int index1 = session.insert("a.b.ins", p);
if(index1>0){
   System.out.println("成功");
}else{
  System.out.println("失败");
}

16 MyBatis 实现修改

  • 在 mapper.xml 中提供 <update> 标签
<update id="upd" parameterType="People">
  update people set name = #{name} where id = #{id}
</update>
  • 编写代码
People peo = new People();
peo.setId(3);
peo.setName("王五");
int index = session.update("a.b.upd", peo);
if(index>0){
    System.out.println("成功");
}else{
    System.out.println("失败");
}
session.commit();

17 mybatis 实现删除

  • 在 mapper.xml 提供<delete> 标签
<delete id="del" parameterType="int">
    delete from people where id = #{0}
</delete>
  • 编写代码
 int  del  =  session.delete("a.b.del",3);

if(del>0){
   System.out.println("成功");
}else{
   System.out.println("失败");
}

session.commit();

18 MyBatis 接口绑定方案及多参数传递

(一)接口绑定

  • 作用: 实现创建一个接口后把 mapper.xml 由mybatis 生成接口的实现类,通过调用接口对象就可以获取 mapper.xml 中编写的 sql。后面 mybatis 和 spring 整合时使用的是这个方案.

  • 实现步骤:

    • 创建一个接口 :例如:LogMapper
      • 接口 Mapper 名和接口名与 mapper.xml 中
        <mapper>namespace 相同

        目录示例:com.mapper包下面包含一个接口:LogMapperLogMapper.xml,然后 LogMapper.xml 中的<mapper>namespace 标签格式为:
        <mapper namespace = "com.mapper.LogMapper> </mapper>
      • 接口中方法名和 mapper.xml 标签的 id 属性相同;
    • 在 mybatis.xml 中使用<package>进行扫描接口和 mapper.xml;
  • 代码实现步骤:

    • 首先在 mybatis.xml 中全局配置文件中的<mappers>下使用<package>
<mappers>
     <package name="com.mapper"/>
</mappers>
  • 然后在 com.mapper 包下新建接口:LogMapper
public  interface  LogMapper  { 
    List<Log>  selAll();
}
  • 然后在 com.mapper 中新建一个 LogMapper.xml
    其中 namespace 的值必须和接口全限定路径(包名+类名)一致,且使用的 id 值必须和接口中方法名相同。同时如果接口中方法为多个参数,可以省略 parameterType
<mapper  namespace="com.mapper.LogMapper">
  <select  id="selAll"  resultType="log"> 
      select  *  from  log
  </select>
</mapper>

绑定的使用:在其它 Java 类中

public class Test {
	public static void main(String[] args) throws IOException {
		InputStream is = Resources.getResourceAsStream("mybatis.xml");
		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
		SqlSession session = factory.openSession();		

		LogMapper logMapper = session.getMapper(LogMapper.class);
		List<Log> list = logMapper.selAll();
		for (Log log : list) {
			System.out.println(log);
		}

(二)多参数实现办法

一共有两种方法:一般方法和使用注解

  • 方法一:一般方法
    首先在在接口 LogMapper 中声明方法
List<Log>  selByAccInAccout(String accin, String accout);

然后在 LogMapper.xml 中添加即可,#{}中可以使用 param1,param2

<select id="selByAccInAccout" resultType="log">
     select * from log where accin=#{param1} and accout=#{param2}
</select>
  • 方法二:可以使用注解方式
    首先在接口中声明方法
/**
mybatis 把参数转换为 map 了,其中@Param("key")  参数内容就是 map 的 value
*/

List<Log> selByAccInAccout(@Param("accin")  String accin123,@Param("accout")  String  accout3454235);

然后在 mapper.xml 中添加

<!--  当多参数时,不需要写 parameterType  -->
<select  id="selByAccInAccout"  resultType="log"  > 
    select * from log where accin=#{accin} and accout=#{accout}
</select>

注:#{} 里面写@Param(“内容”)参数中内容。

多参数的使用
当然 log 是存在数据库中的一个表中,同时要新建实体类的;
在其它 Java 类中使用:

public class Test {
   public static void main(String[] args) throws IOException {
      InputStream is = Resources.getResourceAsStream("mybatis.xml");
      SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
      SqlSession session = factory.openSession();

      LogMapper logMapper = session.getMapper(LogMapper.class);
      List<Log> list = logMapper.selByAccInAccout("3", "1");
      for (Log log : list) {
          System.out.println(log);
      }

      session.close();
      System.out.println("程序执行结束");
    }
}

19 动态 SQL

  1. 根据不同的条件需要执行不同的 SQL 命令,称为动态 SQL
  2. MyBatis 中动态 SQL 的实现是在 实体类mapper.xml 中添加逻辑判断即可
    注:以下的 xml 配置均在 LogMapper.xml 中;

(一)If 使用

<select  id="selByAccinAccout"  resultType="log"> 
      select  *  from  log  where  1=1
<!--  OGNL 表达式,直接写 key 或对象的属性.不需要添加任何特字符号,这里的 and 执行的时候都会被转换为 & -->
      <if  test = "accin != null and accin != ''"> 
          and  accin = #{accin}
      </if>

      <if  test= "accout != null and accout != ''"> 
          and  accout=#{accout}
      </if>
</select>

(二)where 使用

  • 当编写 where 标签时,如果内容中第一个是 and 就会去掉第一个 and;

  • 如果 <where> 中有内容会生成 where 关键字,如果没有内容不生成 where 关键字;

  • 使用示例 :效果:比直接使用 <if> 少写了 where 1 = 1;

<select  id="selByAccinAccout"  resultType="log"> 
    select  *  from  log
  <where>
     <if  test="accin!=null  and  accin!=''"> 
         and  accin=#{accin}
     </if>
  
     <if  test="accout!=null  and  accout!=''"> 
         and  accout=#{accout}
     </if>
  </where>
</select>

(三)choose、when、otherwise 使用

  • 只要有一个成立,其他都不执行

  • 代码示例
    如果 accin 和 accout 都不是 null 或不是””生成的 sql 中只有 where accin=?

<select  id = "selByAccinAccout"  resultType = "log"> 
     select * from log
     <where>
        <choose>
            <when test = "accin != null and accin != ''">
                   and accin = #{accin}
            </when>

            <when test = "accout != null and accout != ''"> 
                   and accout = #{accout}
            </when>
       </choose>
    </where>
</select>

(四)set 使用

  • 作用:去掉最后一个逗号
    <set>用在修改 SQL 中 set 从句,如果<set>里面有内容则生成 set 关键字,没有就不生成

  • 示例
    其中:id=#{id} 目的防止<set>中没有内容,mybatis 不生成 set 关键字,如果修改中没有 set 从句 SQL 语法错误.

<update  id="upd"  parameterType="log"  > 
    update  log
    <set>
        id=#{id},
        <if  test="accIn!=null  and  accIn!=''"> 
            accin=#{accIn},
        </if>

        <if  test="accOut!=null  and  accOut!=''">
             accout=#{accOut},
        </if>
    </set>
    where id=#{id}
</update>

(四) Trim 使用

里面的主要方法如下:

  • prefix 在前面添加内容

  • prefixOverrides 去掉前面内容

  • suffix 在后面添加内容

  • suffixOverrieds 去掉后面内容

  • 执行顺序:首先去掉内容然后添加内容;
    代码示例

<update  id = "upd"  parameterType = "log"> 
    update  log
    <trim prefix = "set" suffixOverrides = ","> 
        a = a,
    </trim> 
    where  id=100
</update>

(五)bind 使用

  • 作用:给参数重新赋值
  • 使用场景:
    • 模糊查询:就是在 SQL 语句中,将用户输入的数据前后加上 % 然后执行语句;见示例
    • 在原内容前或后添加内容 :将用户输入的数据进行格式化之后存入数据库;
<select  id="selByLog"  parameterType="log" resultType="log">
    <bind  name="accin"  value="'%'+accin+'%'"/> 
        #{money}
</select>

(六)foreach标签使用

  • 用于循环参数内容,还具备在内容的前后添加内容,以及添加分隔符功能

  • 适用场景:主要用于 in 查询以及批量新增中(但是 mybatis 中 foreach 效率比较低)

批量新增操作:

  • 默认的批量新增的 SQL 语句为:insert into log VALUES (default,1,2,3),(default,2,3,4),(default,3,4,5)

  • 在执行批处理的时候,需要将 openSession()必须指定下面命令
    factory.openSession(ExecutorType.BATCH);,这里底层 是 JDBC 的 PreparedStatement.addBatch();

  • foreach示例

    • collection=”” 要遍历的集合
    • item 迭代变量, 可以使用#{迭代变量名}获取内容
    • open 循环后左侧添加的内容
    • close 循环后右侧添加的内容
    • separator 每次循环时,元素之间的分隔符
<select  id="selIn"  parameterType="list" resultType="log">
    select  *  from  log  where  id  in
    <foreach  collection="list"  item="abc"  open="(" close=")" separator=",">
        #{abc}
     </foreach>
</select>

(七)<sql><include> 搭配使用

  • 某些 SQL 片段如果希望复用,可以使用 <sql> 定义这个片段
<sql  id="mysql"> 
    id,accin,accout,money
</sql>
  • 可以在 <select><delete><update><insert> 中使用 <include> 引用
<select  id="">
    select  <include  refid="mysql"></include>
    from  log
</select>

20 ThreadLocal

主要优化 service中的方法

  • 线程容器:给线程绑定一个 Object 内容后只要线程不变,可以随时取出。但是一旦改变线程,无法取出内容。

  • 语法示例

final ThreadLocal<String> threadLocal = ThreadLocal<>();
    threadLocal.set("测试");
    new Thread(){
        public  void  run()  {
            String  result  =  threadLocal.get();
            System.out.println("结果:"+result);
        };
    }.start();

21 缓存

  1. 应用程序和数据库交互的过程是一个相对比较耗时的过程;
  2. 缓存存在的意义:让应用程序减少对数据库的访问,提升程序运行效率;
  • MyBatis 中默认 SqlSession 缓存开启

    • 同一个 SqlSession 对象调用同一个 <select> 时,只有第一次访问数据库,第一次之后把查询结果缓存到 SqlSession 缓存区(内存)中
    • 缓存的是 statement 对象.(简单记忆必须是用一个<select>)
      • 在 myabtis 时一个 <select> 对应一个 statement 对象
    • 有效范围必须是同一个 SqlSession 对象
  • 缓存流程

    • 步骤一:先去缓存区中找是否存在 statement
    • 步骤二:如果存在返回结果
    • 步骤三:如果没有缓存 statement 对象,去数据库获取数据
    • 步骤四:数据库返回查询结果
    • 步骤五:把查询结果放到对应的缓存区中

缓存

  • SqlSessionFactory 缓存(二级缓存)
    • 有效范围:同一个 factory 内,哪个 SqlSession 都可以获取
    • 当数据频繁被使用,很少被修改的使用使用二级缓存
    • 使用二级缓存步骤
      • 在 mapper.xml 中添加
      • 如果不写 readOnly=”true”需要把实体类序列化;
        <cache readOnly = "true"></cache>
    • 当 SqlSession 对象 close()时或 commit() 时会把 SqlSession 缓存的数据刷 (flush) 到 SqlSessionFactory 缓存区中

22 MyBatis 实现多表查询

(一)Mybatis 实现多表查询方式

一共三种方式

  • 业务装配:对两个表编写单表查询语句,在业务(Service)把查询的两个结果进行关联;
  • 使用Auto Mapping 特性,在实现两表联合查询时通过别名完成映射;
  • 使用 MyBatis 的 <resultMap> 标签进行实现;

(二)多表查询时,类中包含另一个类的对象的分类

  • 单个对象
  • 集合对象

23 resultMap 标签

相当于现在直接设置映射关系,进行两者(SQL 查询结果和实体类之间)的匹配。

  • <resultMap> 标签写在 实体类Mapper.xml 中,由程序员控制SQL 查询结果与实体类的映射关系;

  • 默认 MyBatis 使用 Auto Mapping 特性进行映射:即保持查询的数据库中的列名和实体类中的属性名相同即可;

  • 使用 <resultMap>标签时,<select>标签不写 resultType 属性,而是使用 resultMap 属性来引用<resultMap>标签.

(一)使用 resultMap 实现单表映射关系

  • 首先进行数据库设计
    示例:数据库表 teacher 中两个字段: id、name

  • 然后进行实体类设计

public class Teacher{
   private int id1;
   private String name1;
}
  • 然后实现 TeacherMapper.xml 代码
<!-- 其中 type 的值为返回值类型-->
<resultMap type="teacher" id="mymap">
    <!-- 主键使用id 标签配置映射关系-->
    <!-- column 值为数据库中列名; property 值为实体类中的属性名 -->
    <id column="id" property="id1" />
    
    <!-- 其他列使用result 标签配置映射关系-->
    <result column="name" property="name1"/>
</resultMap>

<select id="selAll" resultMap="mymap">
      select * from teacher
</select>

上面代码如果使用原来的数据库中字段的名称和实体类相同的话,代码如下:

<select id = "selAll" resultType = "teacher">
    select * from teacher
</select>

(二)使用 resultMap 实现关联单个对象(N+1 方式)

  • N+1 查询方式:先查询出某个表的全部信息,根据这个表的信息查询另一个表的信息;
  • N+1 查询方式与业务装配的区别:
    原来在 service 里面写的代码,现在由 mybatis 完成装配;

实现步骤:

  • 首先在 Student 实现类中包含了一个 Teacher 对象
public class Student {
    private int id;
    private String name;
    private int age;
    private int tid;
    private Teacher teacher;
    // 该 POJO 类中其他信息省略
}
  • 然后在 TeacherMapper.xml 中提供一个查询
<select id="selById" resultType="teacher" parameterType="int">
      select * from teacher where id=#{0}
</select>
  • 最后在下面的 StudentMapper.xml 中完成装配

  • <association> 表示当装配一个对象时使用

  • property: 是对象在类中的属性名

  • select:通过哪个查询查询出这个对象的信息

  • column: 把当前表的哪个列的值做为参数传递给另一个查询

  • 大前提使用 N+1 方式时:如果列名和属性名相同可以不配置,使用 Auto mapping 特性.但是 mybatis 默认只会给列装配一次

<resultMap type="student" id="stuMap">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <result property="tid" column="tid"/>

<!-- 如果关联一个对象,使用 association 标签,调用 teacher 中的查询,如果关联多个对象,使用 collection 标签 -->
// 老师查询中需要一个 Int 类型的参数,这里要通过 column 告诉他传入哪一列的值
    <association property="teacher" select="com.mapper.TeacherMapper.selById"  column="tid">
    </association>
</resultMap>

<select id="selAll" resultMap="stuMap">
      select * from student
</select>
  • 因为这里属性名和字段名相同,因此可以把上面代码简化成
<resultMap type="student" id="stuMap">
    <result column="tid" property="tid"/>
    <!-- 如果关联一个对象-->
    <association property="teacher"
select="com.mapper.TeacherMapper.selById"
column="tid"></association>
</resultMap>

<select id="selAll" resultMap="stuMap">
      select * from student
</select>

(三)使用 resultMap 实现关联单个对象(联合查询方式)

  • 实现只需要编写一个 SQL,在 StudentMapper.xml 中添加下面效果
    • <association/> 只要装配 一个对象就用这个标签
    • 此时把 <association/> 小的看待
    • javaType 属性:装配完后返回一个什么类型的对象.取值是一个类(或类的别名)
<resultMap type="Student" id="stuMap1">
    <id column="sid" property="id"/>
    <result column="sname" property="name"/>
    <result column="age" property="age"/>
    <result column="tid" property="tid"/>
    
    <association property="teacher" javaType="Teacher" >
    <id column="tid" property="id"/>
    <result column="tname" property="name"/>
    </association>
</resultMap>

<select id="selAll1" resultMap="stuMap1">
    select s.id sid,s.name sname,age age,t.id tid,t.name tname FROM student s left outer join teacher t on s.tid=t.id
</select>

(四)N+1 方式和联合查询方式对比

  • N+1:适用于需求不确定时;
  • 联合查询:需求中确定查询时两个表一定都查询;

(五)N+1 名称由来

  • 举例:学生中有 3 条数据

  • 需求:查询所有学生信息及授课老师信息

  • 需要执行的 SQL 命令

    • 查询全部学生信息:select * from 学生
    • 执行 3 遍: select * from 老师 where id=学生的外键
  • 使用多条 SQL 命令查询两表数据时,如果希望把需要的数据都查询出来,需要执行 N+1 条 SQL 才能把所有数据库查询出来;

  • 缺点:效率低

  • 优点:如果有的时候不需要查询学生是同时查询老师,只需要执行一个 select * from student;

  • 适用场景: 有的时候需要查询学生同时查询老师,有的时候只需要查询学生.

  • 如果解决 N+1 查询带来的效率低的问题

    • 默认带的前提: 每次都是两个都查询;
    • 使用两表联合查询;

(六)使用 <resultMap> 查询关联集合对象(N+1 方式)

实现查询老师的时候,把关联的学生也查询出来
以为老师和学生是一对多的关系,因此查询结果是一个集合;

  • 首先在 Teacher 实体类中添加 List
public class Teacher {
    private int id;
    private String name;
    private List<Student> list;
    // 省略其他 get、set 方法
}
  • 然后在 StudentMapper.xml 中添加通过 tid 查询
<select id="selByTid" parameterType="int" resultType="student">
      select * from student where tid=#{0}
</select>
  • 然后在 TeacherMapper.xml 中添加查询全部
    其中 <collection/> 是当属性是集合类型时使用的标签;
<resultMap type="teacher" id="mymap">
    <id column="id" property="id"/>
    <result column="name" property="name"/>

    <collection property="list" select="com.mapper.StudentMapper.selByTid" column="id">
    </collection>    
</resultMap>

<select id="selAll" resultMap="mymap">
      select * from teacher
</select>

(七)使用 <resultMap> 实现加载集合数据(联合查询方式)

  • 首先在 teacherMapper.xml 中添加
    mybatis 可以通过主键判断对象是否被加载过,因此不需要担心创建重复 Teacher
<resultMap type="teacher" id="mymap1">
    <id column="tid" property="id"/>
    <result column="tname" property="name"/>
    <collection property="list" ofType="student" >
        <id column="sid" property="id"/>
        <result column="sname" property="name"/>
        <result column="age" property="age"/>
        <result column="tid" property="tid"/>
    </collection>
</resultMap>

<select id="selAll1" resultMap="mymap1">
    select t.id tid,t.name tname,s.id sid,s.name sname,age,tid from teacher t LEFT JOIN student s on t.id=s.tid;
</select>

24 使用 Auto Mapping 结合别名实现多表查询

只能查询对象,查询结合只能使用上面的方法

  • 只能使用多表联合查询方式,不能使用 N+1 方式
  • 要求:查询出的列名和属性名相同.

实现方式

  • 因为.在 SQL 是关键字符,因此两侧添加反单引号;
<select id="selAll" resultType="student">
    select t.id `teacher.id`, t.name `teacher.name`, s.id id, s.name name, age, tid from student s LEFT JOIN teacher t on t.id=s.tid
</select>

25 MyBatis 注解

注解是写在类中的,不是XML中的,相当于将 实体类Mapper.xml 中的内容直接在对应的接口中实现即可,不在需要 实体类Mapper.xml 文件。

  • 注解:为了简化配置文件;

  • Mybatis 的注解简化 实体类Mapper.xml 文件;

    • 如果涉及动态 SQL 依然使用 mapper.xml
  • mapper.xml 和注解可以共存.

  • 使用注解时要在 mybatis.xml 中标签中使用下面两种方式之一进行配置:

    • <package/>
    • <mapper class=””/>

可以在测试的时候使用下面:因为是接口:

TeacherMapper  tm = session.getMapper(TeachterMapper.class)
List<Teacher> = tm.selAll(info)

具体的使用方式

下面语句直接在 TeacherMapper.java 接口中书写即可

  • 实现查询
@Select("select * from teacher")
List<Teacher> selAll();
  • 实现新增
@Insert("insert into teacher values(default,#{name})")
int insTeacher(Teacher teacher);
  • 实现修改
@Update("update teacher set name=#{name} where id=#{id}")
int updTeacher(Teacher teacher);
  • 实现删除
@Delete("delete from teacher where id=#{0}")
int delById(int id);
  • 使用注解实现功能
    以 N+1 举例

    • 首先在 StudentMapper 接口添加查询
@Select("select * from student where tid=#{0}")
List<Student> selByTid(int tid);
  • 然后在 TeacherMapper 接口添加下面代码
  • @Results() 相当于
  • @Result() 相当于
    - @Result(id=true) 相当与
  • @Many() 相当于
  • @One() 相当于
    @Results(value={
        @Result(id=true,property="id",column="id"),
        @Result(property="name",column="name"),
        @Result(property="list",column="id",many=@Many(select="com.mapper.StudentMapper.selByTid"))
    })
    @Select("select * from teacher")
    List<Teacher> selTeacher();

26 运行原理

(一)MyBatis 运行过程中涉及到的类

  • Resources: MyBatis 中 IO 流的工具类
    • 作用是:加载配置文件
  • SqlSessionFactoryBuilder() : 构建器
    • 作用:创建 SqlSessionFactory 接口的实现类
  • XMLConfigBuilder : MyBatis 全局配置文件内容构建器类
    • 作用:负责读取流内容并转换为 JAVA 代码
  • Configuration: 封装了全局配置文件所有配置信息
    • 全局配置文件内容存放在 Configuration 中
  • DefaultSqlSessionFactory : 是SqlSessionFactory 接口的实现类
  • Transaction : 事务类
    • 每一个 SqlSession 会带有一个 Transaction 对象.
  • TransactionFactory: 事务工厂
    • 负责生产 Transaction
  • Executor : MyBatis 执行器
    • 作用:负责执行 SQL 命令
    • 相当于 JDBC 中 statement 对象(或 PreparedStatement 或 CallableStatement)
    • 默认的执行器 SimpleExcutor
    • 批量操作 BatchExcutor
    • 通过 openSession(参数控制)
  • DefaultSqlSession :是 SqlSession 接口的实现类
  • ExceptionFactory : MyBatis 中异常工厂

(二)流程图

MyBatis执行流程图

(三)文字解释

在 MyBatis 运行开始时需要先通过 Resources 加载全局配置文件.下面需要实例化 SqlSessionFactoryBuilder 构建器.帮助 SqlSessionFactory 接口实现类 DefaultSqlSessionFactory.

在实例化 DefaultSqlSessionFactory 之前需要先创建 XmlConfigBuilder 解析全局配置文件流,并把解析结果存放在 Configuration 中.之后把Configuratin 传递给 DefaultSqlSessionFactory.到此 SqlSessionFactory 工厂创建成功.

由 SqlSessionFactory 工厂创建 SqlSession.

每次创建 SqlSession 时,都需要由 TransactionFactory 创建 Transaction 对象, 同时还需要创建 SqlSession 的执行器 Excutor, 最后实例化DefaultSqlSession,传递给 SqlSession 接口.

根据项目需求使用 SqlSession 接口中的 API 完成具体的事务操作. 如果事务执行失败,需要进行 rollback 回滚事务.

如果事务执行成功提交给数据库.关闭 SqlSession

到此就是 MyBatis 的运行原理.

posted @ 2020-01-01 08:45  默月  阅读(158)  评论(0编辑  收藏  举报