MyBatis学习二
1. MyBatis 介绍
- 1. MyBatis SQL参数传递(掌握)
- 2. (多对一)关联映射(掌握)
- 3. (一对多,多对多)集合映射(掌握)
- 4. SQL映射器Mapper(掌握)
- 5. SSM集成(掌握)
- MyBatis SQL参数传递
#{OGNL表达式} 和 ${OGNL表达式} 的区别
2.1. #{OGNL表达式}
MyBatis会把这个表达式使用?(占位符)替换,作为一个sql参数使用
比如name的值为:
定义SQL: select * from t_user where name = #{name}
最终SQL: select * from t_user where name = ?
2.2. ${OGNL表达式}
MyBatis会把这个表达式的值替换到sql中,作为sql的组成部分;
该方式主要用于程序拼接SQL;
比如name的值为: '1 or 1=1'
定义SQL: select * from t_user where name = ${id}
最终SQL: select * from t_user where name = 1 or 1=1 出现sql注入
如果sql中使用${OGNL},并且参数的类型是(integer,string....)那么OGNL表达式可以写成任意东西;
2.3. Sql注入
预防sql注入,使用占位符查询
select * from employee where username=? and password=?
select * from employee where username='admin' and password='admin'
字符串拼接,出现sql注入
select * from employee where username='admin' and password='0000' or '1'='1'
2.4. ${OGNL}表达式的应用场景:不能用在登录场景,会出现sql注入
用在order by 的场景
Map<String,Object>
map.put("orderBy",'name desc');
map.put("begin",0);
map.put("end",10);
在jdbc的sql代码order by后面不能使用占位符?,只能进行字符串的拼接
2.5. 复杂参数传递
- (多对一)关联映射
3.1. 导入sql文件
CREATE TABLE `t_dept` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`dept_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk1` (`dept_id`),
CONSTRAINT `fk1` FOREIGN KEY (`dept_id`) REFERENCES `t_dept` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3.2. 建立Dept、User的模型
3.3. 配置文件MyBatis-Config.xml
<!-- 单向多对一 -->
<mapper resource="cn/itsource/mybatis/day2/_1_manytoone/DomainMapper.xml" />
3.4. 映射文件DomainMapper.xml
<mapper namespace="cn.itsource.mybatis.day2._1_manytoone.DomainMapper">
<!-- 保存部门 -->
<insert id="saveDept" parameterType="cn.itsource.mybatis.day2._1_manytoone.Dept" useGeneratedKeys="true"
keyProperty="id">
insert into t_dept(name) values(#{name})
</insert>
<!-- 保存用户 -->
<insert id="saveUser" parameterType="cn.itsource.mybatis.day2._1_manytoone.User" useGeneratedKeys="true"
keyProperty="id">
insert into t_user(name,password,dept_id) values(#{name},#{password},#{dept.id})
</insert>
</mapper>
3.5. 保存测试数据
String NAME_SPACE = "cn.itsource.mybatis.day2._1_manytoone.DomainMapper";
@Test
public void testSave() {
SqlSession session = MyBatisUtils.getSession();
// 先保存部门
Dept dept1 = new Dept();
dept1.setName("部门1");
Dept dept2 = new Dept();
dept2.setName("部门2");
session.insert(NAME_SPACE + ".saveDept", dept1);
session.insert(NAME_SPACE + ".saveDept", dept2);
// 在保存用户
for (int i = 1; i < 11; i++) {
User user = new User();
user.setName("user" + i);
user.setPassword("admin");
if (i % 2 == 0) {
user.setDept(dept1); // insert into t_user(dept_id) values(#{dept.id})
} else {
user.setDept(dept2);
}
session.insert(NAME_SPACE + ".saveUser", user);
}
session.commit();
session.close();
}
3.6. 2个表关联查询的方式
3.6.1. 单表查询,不能查询部门的名称
select * from t_user
3.6.2. 等值连接
select u.*,d.*
from t_user u,t_dept d
where u.dept_id=d.id
3.6.3. 内连接
select u.*,d.*
from t_user u
join t_dept d
on u.dept_id=d.id
3.6.4. 左外连接
select u.*,d.*
from t_user u
left join t_dept d
on u.dept_id=d.id
3.7. MyBatis提供两种方式处理我们关联对象,嵌套查询和嵌套结果。
3.8. 测试getAll方法
@Test
public void testGetAll() {
SqlSession session = MyBatisUtils.getSession();
List<User> list = session.selectList(NAME_SPACE + ".getAll");
for (User user : list) {
System.out.println(user);
System.out.println(user.getDept());
}
session.close();
}
3.9. 嵌套结果(发一条左外连接sql解决问题,映射文件Mapper结果的手动封装ResultMap)
使用嵌套结果映射来处理重复的联合结果的子集。--这种方式,所有属性都要自己来!!
<!-- 嵌套结果(发一条左外连接sql解决问题,映射文件Mapper结果的手动封装ResultMap) -->
<select id="getAll" resultMap="userResultMap">
select u.id,u.name,u.password,d.id did,d.name dname
from t_user u
left join t_dept d
on u.dept_id=d.id
</select>
<resultMap type="cn.itsource.mybatis.day2._1_manytoone.User" id="userResultMap">
<!-- 多方User的id -->
<id property="id" column="id" />
<!-- 多方User的属性 -->
<result property="name" column="name" />
<result property="password" column="password" />
<!-- 一方Dept的id和属性 -->
<association property="dept" javaType="cn.itsource.mybatis.day2._1_manytoone.Dept">
<!--一方Dept的id -->
<id property="id" column="did" />
<!--一方Dept的属性 -->
<result property="name" column="dname" />
</association>
</resultMap>
3.10. 嵌套查询(发1查询user+N查询dept条sql解决问题,映射文件Mapper结果的自动封装ResultMap)
通过执行另外一个SQL映射语句来返回预期的复杂类型。
<association ...>
<association ...>
select * from t_department where id = #{id}
- (一对多,多对多)集合映射
4.1. 常见的关系
员工和部门:
在部门方,需要查询到当前部门下的所有员工。----集合查询
一个部门 拥有 多个员工
学生和课程
多 对 多
一个学生 可以学习 多门课程
一门课程 可以有 多个学生
4.2. collection
<collection property="集合数据" ofType="集合中元素类型">
</collection>
4.3. 嵌套结果(一条sql)
嵌套查询在crm项目里面再讲
<mapper namespace="cn.itsource.mybatis.day2._3_onetomany.DomainMapper">
<!-- 嵌套结果(发一条左外连接sql解决问题,映射文件Mapper结果的手动封装ResultMap) -->
<select id="getAll" resultMap="deptResultMap">
select d.id,d.name,u.id uid,u.name uname,u.password
from t_dept d
left join t_user u
on d.id=u.dept_id
</select>
<resultMap type="cn.itsource.mybatis.day2._3_onetomany.Dept" id="deptResultMap">
<!-- 处理一方 -->
<id property="id" column="id" />
<result property="name" column="name" />
<!-- 处理多方 -->
<!-- // 单向一对多 -->
<!-- private Set<User> users = new HashSet<User>(); -->
<collection property="users" javaType="cn.itsource.mybatis.day2._3_onetomany.User">
<id property="id" column="uid" />
<result property="name" column="uname" />
<result property="password" column="password" />
</collection>
</resultMap>
</mapper>
- 嵌套结果和嵌套查询的使用场景
5.1. 嵌套结果:一条sql
用在多对一的表结构
多个数据字典明细和一个数据字典类型
多个员工属于一个部门
多个部门有一个部门经理
5.2. 嵌套查询:1+n条sql
用在一对多或者多对多的表结构
展示数据的时候,在一行第一列显示一方,第二列显示多个多方,
一对多:
部门1 员工1。。。员工n
多对多:
员工1 角色1。。。角色n
- SQL映射器Mapper
MyBatis基于代理机制,可以让我们无需再编写Dao的实现。
6.1. 传统Dao接口,现在名称统一以Mapper结尾
6.2. 接口实现方式一(传统)
6.3. 接口实现方式二(映射器):
6.4. 实现步骤
6.4.1. 根据需求,创建模型相关的Mapper接口(UserMapper)
6.4.2. 编写映射文件
- Mapper。Xml的命名空间,必须和接口的“全限定类名”一致
- 定义sql标签的id,需要和“接口的方法”一致,参数一致,返回值一致
- 缓存
7.1. 默认支持一级缓存,二级缓存需要配置,同JPA一致
缓存是一种典型的“以空间换时间”的策略。
7.2. 一级缓存
SqlSession级别缓存,缓存对象存储周期为第一次获取,到sqlsession被销毁掉,或是sqlSession().clearCache();
7.3. 二级缓存
SqlSessionFactory级别缓存,缓存对象存储周期为第一次获取,到SqlSessionFactory被销毁掉(应用停止了);
默认情况下,只开启一级缓存,如果需要开启二级缓存我们需要在Mapper.xml添加一个<cache>标签和在主配置文件中<setting name="cacheEnabled" value="true"/>;
- 注意:需要缓存的对象,应该实现java.io.Serializable;
- SSM集成
如果要做三大框架集成,我们先保证在一个项目,每一个框架都能够独立运行!!
8.1. 集成SpringMvc
8.1.1. 导入21个jar文件(包含spring的jar)
aop,tx,jdbc,web,webmvc
dbcp,fileupload,jackson
8.1.2. applicationContext.xml
Spring使用一个配置,SpringMVC使用另一个配置
1个,组件扫描,<context:component-scan base-package="cn.itsource.ssm" />
<import resource="classpath:applicationContext-mvc.xml" />
8.1.3. applicationContext-mvc.xml 4个
mvc静态资源放行,mvc注解支持
视图解析器,上传解析器
<!-- 开启spring对springmvc的注解支持 -->
<mvc:annotation-driven />
<!-- 对于静态资源(图片,css,js)进行放行 -->
<mvc:default-servlet-handler />
<!-- 设置视图路径的前后缀,该配置可以让我们写视图路径的时候更简单。 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--前缀: jsp在当前工程文件夹的路径 -->
<property name="prefix" value="/WEB-INF/views/" />
<!--后缀:扩展名 -->
<property name="suffix" value=".jsp" />
</bean>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为1MB -->
<property name="maxUploadSize">
<!-- spring el写法:5MB -->
<value>#{1024*1024*5}</value>
</property>
</bean>
8.1.4. web.xml
1.post乱码过滤器
2.springmvc核心控制器
<!-- post提交中文乱码的过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!-- 按照什么格式进行转码 -->
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 核心控制器 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
8.2. 集成MyBatis环境
8.2.1. 加入MyBatis相关9个 jar包(mybatis-spring-1.2.0.jar)
- 核心包
- 依赖包(删除一个commons-logging-1.1.1.jar)
- 数据库连接包
8.3. Spring和MyBatis的集成方式
8.3.1. 框架集成核心
如果你的项目中,用到了Spring框架,那么其他框架主要就是和Spring集成!!
8.3.2. 和Spring集成的顺序
- 把当前框架的核心类,交给Spring管理
- 如果框架有事务,那么事务也要统一交给Spring管理
8.4. Spring配置文件
8.4.1. jdbc配置文件&数据源dataSource
<!-- Jdbc配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 数据源dataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 依赖注入连接池需要的属性 -->
<!-- property name="是BasicDataSource的set方法,本质属性" -->
<!-- property value="是jdbc.properties配置文件的key" -->
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!--maxActive: 最大连接数量 -->
<property name="maxActive" value="150" />
<!--minIdle: 最小空闲连接 -->
<property name="minIdle" value="5" />
<!--maxIdle: 最大空闲连接 -->
<property name="maxIdle" value="20" />
<!--initialSize: 初始化连接 -->
<property name="initialSize" value="30" />
<!-- 连接被泄露时是否打印 -->
<property name="logAbandoned" value="true" />
<!--removeAbandoned: 是否自动回收超时连接 -->
<property name="removeAbandoned" value="true" />
<!--removeAbandonedTimeout: 超时时间(以秒数为单位) -->
<property name="removeAbandonedTimeout" value="10" />
<!--maxWait: 超时等待时间以毫秒为单位 1000等于60秒 -->
<property name="maxWait" value="1000" />
<!-- 在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. -->
<property name="timeBetweenEvictionRunsMillis" value="10000" />
<!-- 在每次空闲连接回收器线程(如果有)运行时检查的连接数量 -->
<property name="numTestsPerEvictionRun" value="10" />
<!-- 1000 * 60 * 30 连接在池中保持空闲而不被空闲连接回收器线程 -->
<property name="minEvictableIdleTimeMillis" value="10000" />
<property name="validationQuery" value="SELECT NOW() FROM DUAL" />
</bean>
8.4.2. SqlSessionFactoryBean
SSJ集成时候需要entityManagerFactory
SSM集成时候需要sqlSessionFactory
resources\MyBatis集成Spring文档资料\mybatis-spring整合文档.exe
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
8.4.3. ssm注入顺序
jdbc.properties->dataSource->sqlSessionFactory->mapper(dao)->service->controller(action)
8.4.4. 获取sqlSessionFactory方案1
<!-- Mybatis的核心类 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
<property name="mapperInterface" value="cn.itsource.ssm.mapper.UserMapper" />
</bean>
<context:component-scan base-package="cn.itsource.ssm"/>
8.4.5. 获取sqlSessionFactory方案2
<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource" />
<!-- 配置mybatis (mapper)映射器路径 -->
<property name="mapperLocations" value="classpath:cn/itsource/ssm/mapper/*Mapper.xml" />
<!-- 配置mybatis 类型别名 -->
<property name="typeAliasesPackage">
<value>
cn.itsource.ssm.domain
Cn.itsource.ssm.query 可能有查询对象
</value>
</property>
</bean>
一劳永逸
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.itsource.ssm.mapper"></property>
</bean>
- 课程总结
9.1. 重点
- SSM集成,操作必须非常熟练
- 多对一嵌套结果,嵌套查询
- 一对多嵌套结果
- 映射器接口
9.2. 难点
- 多对一嵌套结果:查询的有dept表的别名配置
- 映射文件里面使用${}的适用场景:order by
- 常见异常
- Caused by: java.io.NotSerializableException: cn.itsource.mybatis.day2._5_cache.User
要使用二级缓存,要求domain必须实现序列化接口
- 课后练习
- 面试题
- 扩展知识或课外阅读推荐
13.1. 扩展知识
- java.io.Serializable浅析
http://www.cnblogs.com/gw811/archive/2012/10/10/2718331.html
13.2. 课外阅读