1. 回顾mybatis的自定义再分析和环境搭建+完善基于注解的mybatis
2. mybatis的curd(基于代理dao的方式)
3. mybatis中的参数深入及结果集的深入
4. mybatis中基于传统dao的方式(编写dao的实现类)——-了解的内容
5. mybatis中的配置(主配置文件:SqlMapConfig.xm1)
1) properties标签
2) typeAliases标签
3) mappers标签
1、回顾
1) 回顾自定义Mybatis的流程分析,这部分参考文档《Mybatis第二天讲义.pdf》,视频1。
2) 基于注解的自定义再分析,参考视频2;
3) 回顾Mybatis的环境搭建,-实现查询所有的功能(视频4),具体流程如下图。这部分不需要记忆,忘记了重新回来看即可,做多了就记住了。
下面是XML配置
namespace+findAll方法名——可以通过某个DAO接口中的某个方法,来在Mybatis中唯一确定要执行的方法。而SQL语句用于执行,结果封装对象的全限定类名用于封装最后返回的结果。
下面是注解配置
XMLConfigBuilder如何获取封装结果的实体类的全限定类名(视频3-3.50).
2、Mybatis的CRUD
创建一个mybatis_day02_CRUD项目,然后向前面那样设置pom.xml以及各类配置文件,创建各种文件。下面使用XML配置的方法来实现:
1、在UserDao中添加一个方法:void saveUser(User user);
2、在UserDao.xml配置文件中添加相应的标签配置:
<insert id="saveUser" parameterType="com.lkj.domain.User">
insert into user(username,address,sex,birthday) values(#{username} , #{address} , #{sex} ,#{birthday});
</insert>
3、设置测试类进行测试测试类代码贴在最下面
注意:主配置文件SqlMapConfig.xml已经设置映射配置文件的位置,而且目前只有一个映射配置文件UserDao.xml,因此无需再次设置。
其他操作大同小异,具体注释在代码中(项目mybatis_day02_CRUD代码),看代码与相应的视频即可,这里不再赘述。
#{}与KaTeX parse error: Expected 'EOF', got '#' at position 18: …的区别(见讲义——2.5.5 #̲{}与{}的区别)
#{}表示一个占位符号
通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,#{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类
型值, #{}括号中可以是 value 或其它名称。
${}表示拼接 sql 串
通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值, ${}括号中只能是 value。
模糊查询的 v a l u e 源 码 分 析 ( 见 讲 义 — — 2.5.6 模 糊 查 询 的 {value}源码分析(见讲义——2.5.6 模糊查询的 value源码分析(见讲义——2.5.6模糊查询的{value}源码分析 以及 视频8-3.00解析)
关于模糊查询2种赋值方式的分析
1、使用:select * from user where username like '%${value}%'; 方式,执行的语句是:select * from user where username like '%王%';
---这种方式使用的是statement对象的字符串的拼接SQL方式
2、使用:select * from user where username like #{username}; 方式,执行的语句是:select * from user where username like ?
---这种方式使用的是preparedStatement的参数占位符的方式。(实际开发中一般使用这种方式)
问题扩展:新增用户 id 的返回值(参考《讲义》——2.2.4 问题扩展:新增用户 id 的返回值 与 视频9)
新增用户后, 同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment 的值返回。
keyProperty表示对应的属性名称是id,resultTypes是返回值类型,order是表示什么时候进行操作(after,表示在插入数据之后再获取自增长的id)
<insert id="saveUser" parameterType="com.lkj.domain.User">
<selectKey keyColumn="id" keyProperty="id" order="AFTER" resultType="int">
select last_insert_id();
selectKey>
insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
insert>
3、Mybatis 的参数深入
ognl表达式:它是 apache 提供的一种表达式语言, 全称是:Object Graphic Navigation Language 对象图导航语言,它是按照一定的语法格式来获取数据的。语法格式就是使用 #{对象.对象}的方式。之前保存一个用户数据到数据库的时候使用的就是ognl表达式。
<insert id="saveUser" parameterType="com.itheima.domain.User">
insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
insert>
由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。它用的是 ognl 表达式。#{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类类型与名称,所以可以省略 user,而直接写 username。
OGNL表达式是通过对象的取值方法来获取数据。在写法上把get给省略了。
比如:我们获取用户的名称类中的写法:user.getUsername();
OGNL表达式写法:user.username
mybatis中为什么能直接写username,而不用user.呢?
因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名。
POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans。
SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类。
注意:
基 本 类 型 和 String 我 们 可 以 直 接 写 类 型 名 称 , 也 可 以 使 用 包 名 . 类 名 的 方 式 , 例 如 :java.lang.String。实体类类型,目前我们只能使用全限定类名。
究其原因,是 mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在今天课程的最后一个章节中将讲解如何注册实体类的别名。
见文档——3.2 传递 pojo 包装对象、视频10解析、以及代码中的解析。
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
既我们将多个对象组成一个包装对象(如QueryVo),然后将这个包装对象作为查询方法的参数,这样就可以完成多种查询条件的查询。
4、Mybatis 的输出结果封装
见文档——第4章 Mybatis 的输出结果封装、视频11解析、以及代码中的解析。
resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型(实体类对象,或者实体类对象的列表List<实体类>)。
我们之前要求实体类属性名与数据库的列名保持一致,我们现在修改实体类的属性的类名,使他们与数据库类名不一致:
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
发现很多地方报错,先将MybatisTest 中给属性赋值的地方的错修改(将属性修改为我们修改后的属性)。
接下来将UserDao.xml中对条件进行赋值的错误修改。先将 插入与更新的 SQL语句中的赋值的部分修改为我们实体类修改后的属性。如下:
insert into user(username,address,sex,birthday) values(#{userName} , #{userAddress} , #{userSex} ,#{userBirthday});
update user set username=#{userName} , address=#{userAddress} , sex=#{userSex} , birthday=#{userBirthday} where id =#{userId};
user(username,address,sex,birthday):对应表的属性
values(#{userName} , #{userAddress} , #{userSex} ,#{userBirthday}):对应实体类的属性
我们发现这样修改完成之后,插入和更新数据的时候,实体类的属性与数据库表的属性可以对应得上,这样插入与更新数据都可以执行!
(注意修改selectKey标签中对应实体类userId属性的: keyProperty="userId")
此时根据id删除的方法没有影响,其语句是:
delete from user where id = #{uid};
因为id可以对应表中的id,因此可以对应得上
此时使用聚合函数查询所有用户数量的方法没有影响,其语句是:
select count(id) from user;
使用user表中的id作为查询条件,可以对应得上
但是对于查询的方法,我们发现运行后有问题。运行查询所有用户的方法,结果如下:
我们发现只有userName数据查询出来,其他的都没有查询出来。
因为我们的查询语句是:select * from user; ,将数据库user表中所有的属性值查询出来后,要封装到结果对象User中,但是User的属性与表的属性对应不上,因此数据封装不进去。但是Windows系统中,MYSQL在封装数据的时候不区分大小写,因此表的username属性的值可以封装到User对象的的userName属性中。但是其他的封装不进去。
注意,在Linux系统下,MYSQL严格区分大小写,此时userName的数据也封装不进去。
此时有2种解决方法
1、使用as属性起别名
将查询所有的语句:select * from user; 修改为:
select id as userId,username as userName,address as userAddress,sex as userSex,birthday as userBirthday from user;
既我们将从user表中查询出来的id属性,起个别名为userId(其他属性相同),这样MYSQL在将id属性封装到User类的属性的时候,就会使用userId去匹配,这样就可以匹配上,将数据封装到User对象中。
对于其他查询方法 ,如根据userName进行模糊查询
select * from user where username like #{userName};
(#{userName}属性已经修改为我们User对象中对应的属性)
由于查询结果封装的时候user表中的属性与User的属性对应不上,所以应该修改。
select id as userId,username as userName,address as userAddress,sex as userSex,birthday as userBirthday from user where username like #{userName};
这样,封装结果的时候user表中的属性与User的属性对应得上,就可以封装
说明:这种方式在SQL语句的层面解决问题,效率最高,但是这种方法对于每一个需要将结果封装到User对象的语句,都需要给user表属性起一个与User类属性相同的别名。
2、配置的方式
如下,我们在UserDao.xml中添加一个配置的标签,建立user表属性与User对象属性的对应关系
<!-- 配置 查询结果的列名和实体类的属性名的对应关系
id的名字可以随便取,type表示结果最后要封装到的实体类(使用全限定类名)
此处,column是数据库表中对应的属性,property是实体类中对应的属性
-->
<resultMap id="userMap" type="com.lkj.domain.User">
<!-- 主键字段的对应 -->
<id property="userId" column="id"></id>
<!--非主键字段的对应-->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</resultMap>
下面我们将 需要将查询结果封装到User对象的方法的resultType="com.lkj.domain.User"属性,修改为我们上面指定的结果标签:resultMap="userMap"。 这样我们就可以使用上面定义的结果类型的对应。
此时查询所有的查询语句依然是:select * from user; 倜然可以将结果封装到User对象。
这种方法需要多解析一段xml,执行效率较低。但是我们只要将 那些将结果封装到User对象的方法 的结果resultType="com.lkj.domain.User"属性修改为我们定义的映射resultMap="userMap"即可,既开发效率高。
如根据userName模糊查询的语句仍然是:select * from user where username like #{userName};
我们将其结果属性修改为:resultMap="userMap" ,成功将模糊查询的结果封装到User对象。
(具体见视频11 与 代码的解析)。
5、Mybatis 传统 DAO 层开发
使用 Mybatis 开发 Dao,通常有两个方法,即原始 Dao 开发方式(编写实现类)和 Mapper 接口代理开发方式。 而现在主流的开发方式是接口代理开发方式,这种方式总体上更加简便。
这部分不是很重要,属于了解部分的内容,我们参考视频13-视频15与《文档——第5章 Mybatis 传统 DAO 层开发[了解] 》 的解析即可!不必深入了解。
注意
1)使用Dao实现类的方法,也需要dao.xml文件,是使用注解的方式开发才不需要dao.xml实现类。
2)程序中的事务是为了保证业务上两个及以上关联的DML的正确,即数据一致性。单个DML语句,数据库会对之开启事务。而查询一般是不需要事务的,除非这个查询与DML有关,比如对查询出的数据在下一个操作进行修改,那就得加锁。
编写DAO实现类的执行过程分析(见视频16-18解析,这部分很重要,注意理解!)。这里需要分析SqlSession的方法,需要进入到SqlSession接口实现类DefaultSqlSession的源码中。
1、如何找到一个接口的默认实现类:CTRL+点击进入到SqlSession接口的源码中,随便找个SqlSession,光标移到SqlSession——右键diagrams-show diagram ,找到SqlSession,右键——show implemetation ,找到实现类DefaultSqlSession,点击它,上面出现这个实现类,然后双击,就可以打开实现类。(视频16-9.00)
2、注意,使用视频介绍的断点调试的方式来跟踪源码中所涉及的类与接口以及方法。
3、在DefaultSqlSession中,insert方法调用了update方法。其实CUD的方法都是最后执行一下,只是传入的语句不一样,语句的参数不一样,从这点上分析,CUD操作只需要一个方法就够了(update).
insert、delete、update最后走的都是DefaultSqlSession中的update方法。(事实上,在实际开发过程中,insert、delete、update3个方法都可以使用update方法)
** Mybatis中使用代理Dao执行过程的分析(见视频19解析,这部分很重要,注意理解!)**
Mybatis之所以可以不写DAO,是因为它在想办法调用我们自己实现DAO时所调用的方法。如下图分析最后调用的是selectList方法。
6、SqlMapConfig.xml配置文件
SqlMapConfig.xml 中配置的内容和顺序如下:
-properties(属性)
--property
-settings(全局配置参数,如二级缓存)
--setting
-typeAliases(实体类类型别名)
--typeAlia
--package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
--environment(环境子属性对象)
---transactionManager(事务管理)
---dataSource(数据源)
-mappers(映射器)
--mapper
--package
我们可以将JDBC的配置文件jdbcConfig.properties放到项目的resources目录下面,这个文件放的就是JDBC的连接信息
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ee42
jdbc.username=root
jdbc.password=1234
随后我们在SqlMapConfig.xml文件的 标签中引用jdbcConfig.properties配置文件的信息。然后再下面的 标签中,直接引用我们导入的jdbcConfig.properties文件中键对应的值即可使用。(测试的时候注意将 jdbcConfig.properties 中的密码修改为我们自己数据库的密码,将数据库修改为我们自己的数据库)
如下代码:
<configuration>
<properties url="file:///G:/idea_java_project/mybatis_day02_CRUD/src/main/resources/jdbcConfig.properties">
properties>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC">transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="com/lkj/dao/UserDao.xml" />
mappers>
configuration>
前面UserDao.xml中,parameterType=“类型” 中类型如果为整型,可以写为Integer、int\java.lang.Integer等多种形式,这是因为Mybatis中给各种基本数据类型都起了别名。当然,我们也可以通过 typeAliases标签来配置别名。
typeAliases标签,它只能配置domain中类的别名。(在SqlMapConfig.xml文件中起)
当我们要指定别名的类很多的时候(比如在com.lkj.domain包下有很多个需要指定别名的类),可以使用package标签。
<?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>
<!--配置properties
可以在标签内部配置连接数据库的信息。也可以通过属性引用外部配置文件信息.
其中:
resource属性: 常用的
用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。
我们将jdbcConfig.properties文件放到类路径 src/resources 下,就可以使用resource属性配置
url属性:
是要求按照Url的写法来写地址
URL:Uniform Resource Locator 统一资源定位符。它是可以唯一标识一个资源的位置。
它的写法:
http://localhost:8080/mybatisserver/demo1Servlet
协议 主机 端口 URI
URI:Uniform Resource Identifier 统一资源标识符。它是在应用中可以唯一定位一个资源的。
这里我们在使用的时候,将项目工作空间的jdbcConfig.properties文件拖到浏览器中,就可以得到下面的url路径。
注意,必须要加签名的协议:file:///
-->
<!--<properties resource="jdbcConfig.properties">-->
<properties url="file:///G:/idea_java_project/mybatis_day02_CRUD/src/main/resources/jdbcConfig.properties">
<!--
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/lkj_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
-->
</properties>
<!--使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<!--typeAlias用于配置别名。type属性指定的是实体类全限定类名。alias属性指定别名,当指定了别名就不再区分大小写。
我们dao.xml文件中使用到 com.lkj.domain.User 的地方,可以修改为:user(USER,uSEr等等)
-->
<!--<typeAlias type="com.lkj.domain.User" alias="user"></typeAlias>-->
<!-- 用于指定要配置别名的包(这里package是用于指定封装实体类的包domain),当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
<package name="com.lkj.domain"></package>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<!--
上面<properties>标签已经设置了各个属性的值,我们下面只需要使用: ${属性名} 引用即可.
注意,这里如果使用导入的配置文件,那么这里的属性名应该与配置文件中的键名相同!如:${jdbc.driver}
-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件。-->
<mappers>
<!--<mapper resource="com/lkj/dao/UserDao.xml" />-->
<!-- package标签是用于指定dao接口所在的包,当指定了之后就不需要再写mapper以及resource或者class了
这样设置,主配置文件一样可以找到dao接口以及dao接口对应的配置文件dao.xml。
-->
<package name="com.lkj.dao"></package>
</mappers>
</configuration>