应用框架@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
-
-
业务逻辑
-
控制器
-
视图
-
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)+" "+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 换行
上面可以多个一起使用, 中间空格就行
示例如下:
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 或者对象
- 使用索引,从 0 开始
<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 中提供
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
包下面包含一个接口:LogMapper
和LogMapper.xml
,然后LogMapper.xml
中的<mapper>
namespace 标签格式为:
<mapper namespace = "com.mapper.LogMapper> </mapper>
- 接口中方法名和 mapper.xml 标签的 id 属性相同;
- 接口 Mapper 名和接口名与 mapper.xml 中
- 在 mybatis.xml 中使用
<package>
进行扫描接口和 mapper.xml;
- 创建一个接口 :例如:
-
代码实现步骤:
- 首先在 mybatis.xml 中全局配置文件中的
<mappers>
下使用<package>
- 首先在 mybatis.xml 中全局配置文件中的
<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
- 根据不同的条件需要执行不同的 SQL 命令,称为动态 SQL
- 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 语句中,将用户输入的数据前后加上
%
然后执行语句;见示例 - 在原内容前或后添加内容 :将用户输入的数据进行格式化之后存入数据库;
- 模糊查询:就是在 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 缓存
- 应用程序和数据库交互的过程是一个相对比较耗时的过程;
- 缓存存在的意义:让应用程序减少对数据库的访问,提升程序运行效率;
-
MyBatis 中默认 SqlSession 缓存开启
- 同一个 SqlSession 对象调用同一个
<select>
时,只有第一次访问数据库,第一次之后把查询结果缓存到 SqlSession 缓存区(内存)中 - 缓存的是 statement 对象.(简单记忆必须是用一个
<select>
)- 在 myabtis 时一个
<select>
对应一个 statement 对象
- 在 myabtis 时一个
- 有效范围必须是同一个 SqlSession 对象
- 同一个 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 运行开始时需要先通过 Resources 加载全局配置文件.下面需要实例化 SqlSessionFactoryBuilder 构建器.帮助 SqlSessionFactory 接口实现类 DefaultSqlSessionFactory.
在实例化 DefaultSqlSessionFactory 之前需要先创建 XmlConfigBuilder 解析全局配置文件流,并把解析结果存放在 Configuration 中.之后把Configuratin 传递给 DefaultSqlSessionFactory.到此 SqlSessionFactory 工厂创建成功.
由 SqlSessionFactory 工厂创建 SqlSession.
每次创建 SqlSession 时,都需要由 TransactionFactory 创建 Transaction 对象, 同时还需要创建 SqlSession 的执行器 Excutor, 最后实例化DefaultSqlSession,传递给 SqlSession 接口.
根据项目需求使用 SqlSession 接口中的 API 完成具体的事务操作. 如果事务执行失败,需要进行 rollback 回滚事务.
如果事务执行成功提交给数据库.关闭 SqlSession
到此就是 MyBatis 的运行原理.