使用JDBC开发,有一些缺陷:
1.JDBC的代码是重复的(贾琏欲执事). 2.SQL写死了.参数需要手动的设置.(硬编码). 把SQL写到配置文件中. 3.结果集解析不通用. 把javabean的属性名作为列名获取数据,再设置到对象的属性中.-----内省. 前提:属性名需要根列名一一匹配. |
什么是ORM?
对象关系映射(Object Relational Mapping,简称ORM):
是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将java程序中的对象自动持久化到关系数据库中。
mybatis解决了什么问题?
1.消除几乎所有的jdbc的代码
2.参数的手工设置
3.结果集的处理
mybatis不能做的事情是什么?
1.连接数据库的四要素
2.sql语句
3.启动框架
MyBatis的使用
1.导入jar包
2.编写dao
3.编写mybatis主配置文件
4.编写mapper映射文件
5.实现测试
实现:
面向接口编程
调用方法必须指明方法的具体位置
(mapper.xml中namesapce的名字.方法名)
增删改必须提交事务
所有资源必须关闭
注意事项:
连接池只需要一个 工具类 静态方法
加载资源:factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
XML文件中四要素也存在硬编码问题:
将四要素抽取为配置文件 db.properties
xml文件中使用el表达式${}获取配置文件中的值
可以解决频繁更改xml文件的问题
连接池只需要一个
使用工具类 静态代码块来加载资源
关于别名:
常用方法:
在主配置文件中直接 给domain包取别名 这样可以直接使用类的简单名字
关于resultMap:
目的是解决表名与属性名不一致的问题
实现代码:
@Override
public void save(Employee e) {
SqlSession session = MybatisUtil.getSession();
session.insert("com.mybatis.dao.EmployeeMapper.save", e);
session.commit();
session.close();
}
@Override
public List<Employee> listAll() {
SqlSession s = MybatisUtil.getSession();
List<Employee> selectList = s.selectList("com.mybatis.dao.EmployeeMapper.listAll");
s.close();
return selectList;
}
关键:
主配置文件(框架只会加载主配置文件):
1.起名:mybatis-config.xml.
2.路径:放在classpath下面.创建一个source folder : resources.
3.参考文档: mybatis中文文档/XML 映射配置文件章节.
4.主要内容:不能乱写,因为配置文件中的内容是一定要让框架解析的.
约束头:
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
如果没有网络,需要配置离线的约束,如下图.
<?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>
<!-- default 默认使用哪一个 环境-->
<environments default="dev">
<environment id="dev">
<!-- type:事务提交 -->
<transactionManager type="jdbc"></transactionManager>
<!-- 是否使用连接池 -->
<dataSource type="POOLED">
<!-- driver的名字 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///sss" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<mappers>
<!-- 告诉框架去哪里找mapper的映射文件 -->
<mapper resource="com/mybatis/mapper/EmployeeMapper.xml" />
</mappers>
</configuration>
mapper映射文件
EmployeeMapper.xml
(与EmployeeMapper.java接口在同一个包下)
<?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文件的唯一标志,可以通过该名字找到指定的mapper.方法 -->
<mapper namespace="com.mybatis.dao.EmployeeMapper">
<!-- id:每一条sql的唯一的标识符,不能重复 -->
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT
INTO employee (name,salary)VALUES(#{name},#{salary})
</insert>
<delete id="delete">
DELETE FROM employee WHERE id=#{id}
</delete>
<update id="update">
UPDATE employee SET name=#{name},salary=#{salary}
WHERE id=#{id}
</update>
<!--任意一个查询操作都要告诉框架将查询出来的每一行数据封装成什么类型的对象 -->
<select id="select" resultType="com.mybatis.domain.Employee">
SELECT * FROM employee WHERE
id=#{id}
</select>
<select id="listAll" resultType="com.mybatis.domain.Employee">
SELECT *FROM employee
</select>
</mapper>
2.核心内容:
1.mapper接口与原理
问题:需要写xxximpl实现类,写查找sql的语句,容易写错
SqlSession s=MybatisUtil.getSession();
s.insert("com.pmis.mapper.ProductMapper.save",p);
s.commit();
s.close();
mybatis使用动态代理创建mapper实现类
在以后的开发中 只需要写mapper接口 和mapper.xml文件
直接在mapper接口定义相应的方法
public interface UserMapper {
User getByUsername(String username);
}
一个实体类对应一个mapper接口,一个接口对应一个mapper.xml文件
注意:mapper中的sql id一定要和mapper接口中的方法名一样
<select id="getByUsername" resultType="User">
SELECT *FROM user WHERE username=#{username}
</select>
执行sql
public void testselect() throws Exception {
SqlSession session = MyBatisUtil.getSession();//获取sqlsession对象
UserMapper userMapper = session.getMapper(UserMapper.class);//获取mapper
//此处,mapper是一个接口,调用方法应该用实现类的对象去调用,此时我们没有写实现类,
// mybatis使用动态代理的方式帮我们做了,通过namespace+mapper中的方法名找到对应的sql语句
User user=userMapper.getByUsername("admin");//调用mapper中的方法
session.close();
System.out.println(user);
}
2.多参数问题
在动态代理中也和之前一样需要去找到相应sql的位置,并且传入参数,但是参数只能传入一个,解决这个问题有两种方式
手动封装:相应的对象,map集合
自动封装:@Param () 注解:自动将参数封装到map集合中
User getByUsernameAndPassword(@Param("username")String username,@Param("password")String password);
3.#,&的区别
#
参数会先转换为 ? ,会加上引号,支持简单类型
使用预编译语句对象
$
不会判断 直接拼接到sql中 GROUP BY … 不支持简单类型
使用statement语句对象,存在sql注入问题
除了要分组,排序的 其余的都用#
4.动态sql
1.if中的属性,javaBean或者map中
<if test="minSalary !=null">//minSalary 是javaBean里的或者map中的,如果不是JavaBean则在接口中传入参数时使用@Param注解封装到map中
WHERE salary >=#{minSalary}
</if>
2.
set可以自动添加set 关键字,删除无关的逗号,若条件都不成立,自动去除set关键字
<update id="update">
<set>
<if test="username !=null">
username=#{username},
</if>
<if test="password !=null">
password=#{password},
</if>
WHERE id=#{id}
</set>
</update>
3.trim
自定义修改的内容,去掉什么,添加什么
4.批量删除
使用 foreach标签
collection:目标数组或者集合,集合默认使用 list,数组默认使用 array
open:迭代开始符号
close:迭代结束符号
separator:分隔符号
item:迭代元素变量
index:迭代索引变量
void delete(Long[] ids);//接口中传入一个数组
xml中sql:传入的是数组,在cllection中使用array,不能使用数组名 ids
<delete id="delete">
DELETE FROM user WHERE id IN
<foreach collection="array" open="(" separator="," item="id" close=")">
#{id}
</foreach>
</delete>
5.批量添加
5.关系
关联关系:(一对一,一对多,多对一,多对多)
1.多对一
(绝大部分都是这种关系,单向的多对一)
举例说明关联关系:
添加一个部门和两个员工,这两个员工属于这个部门的
1.表中的关系(通过many方外键列将两张表联系起来)
部门表:id,name
员工表:id,name,dept_id(外键列)
2.java中(通过成员变量的方式将两个实体类关联起来)
public class Employee{
private Long id;
private String name;
private Department dept;
}
3.使用mapper规范完成相应的配置
①编写DepartmentMapper.xml中的insert时应当获取自动生成的主键,否则employee中dept_id没有值
<insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
INSERT INTO dept (name)VALUES(#{name})
</insert>
4.额外SQL查询(查询员工信息时,部门信息为null)
①表中列为dept_id,对象中是一个department类的对象
使用resultMap将部门id查询出来
<result column="deptId" property="dept.id" />
②查询部门名称
手动发送sql:根据查询出来的部门id再写一条sql查询出部门名称
自动发送sql:association 发送额外 SQL
javaType:额外查询出来的结果集封装成什么对象
cloumn:列的值传递,额外sql的参数值
select:额外sql语句
property:属性名
<resultMap type="Employee" id="base">
<id column="id" property="id" />
<result column="name" property="name" />
<association property="dept"
column="dept_id"
javaType="Department"
select="com.champion.mapper.DepartmentMapper.selectByPrimaryKey"/>
</resultMap>
association :查询的结果集是一个对象,select返回的是一个单个对象
5.延迟加载:需要使用到需要的数据时才会发送sql去查询
问题:查询员工信息时不需要使用部门信息时,也会发送两条sql,性能不好
①在mybatis-config主配置文件中开启延迟加载
②设置延迟加载的属性
③设置哪些方法能够触发延迟加载
<settings>
<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- true表示积极加载,任何方法的调用都会加载该对象的所有属性
false:表示只有用到对象的关联属性才会被加载,并发送额外sql -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 指定哪些方法触发一次延迟加载,默认是:clone,equals,toString,hashCode -->
<setting name="lazyLoadTriggerMethods" value="clone"/>
</settings>
④注意<settings>的位置:位于配置文件的第二属性配置位置 (properties 后 typeAliases前)
6.额外sql的N+1问题(当员工表中有N条数据,此时查询所有员工时,每个员工都关联着一个部门,此时就会发送N+1条sql语句)
1:查询列表
select *FROM employee
N:每个员工的部门
select *FROM dept WHERE id=?
这样非常消耗性能,此时应当采用关联查询,一条sql查询出所有需要的数据
select e.id,e.name,d.id as d_id,d.name as d_name FROM employee e join dept d on e.dept_id=d.id
如何封装数据到对象中:
需要将的dept表中的数据查询出来封装到Department对象dept中
针对关联属性是单对象(非集合类型)类型,使用 association 元素,通常直接使用多表查询操作。
针对关联属性是集合对象类型,使用 collection 元素,通常使用额外 SQL 处理,配置延迟加载。
需要将员工的信息和部门的信息都封装成员工类型,此时员工的列名 id,name 和部门列名相同,解决列名相同的问题使用设置别名的方式,在映射时使用 columnPrefix 元素解决 d_ 重复的问题
<resultMap id="baseMapper" type="Employee">
<result property="id" column="id" />
<result property="name" column="name" />
<association property="dept" javaType="Department" columnPrefix="d_">//
<result property="id" column="id" />
<result property="name" column="name" />
</association>
</resultMap>
2.一对多
eg:保存一个部门和两个员工,且这两个员工都是这个部门的(一个部门有多个员工)
1.表中的关系(通过外键列将两张表联系起来,与多对一相同)
2.java中的关系(通过成员变量的方式联系两个实体类,one方维护关系,使用集合)
public class Department {
private Long id;
private String name;
List<Employee> emps=new ArrayList<>();
}
3.根据实体类属性编写相应的insert语句sql
将对象添加到集合中,但此时添加的员工部门信息为NULL,此时在many方(员工)添加属性:部门id(deptId),手动将部门id设置给员工
e1.setDeptId(d.getId());
此时需要注意:手动设置部门id操作在添加部门操作之后,且在insert中需要设置获取自动递增的主键,不然获取不到部门id,d.getId()为NULL
4.一对多的额外sql查询
eg:查询部门,并把其部门的员工信息也查询出来
先查一个部门,再通过部门id查询该部门的所有员工,当需要查询该部门下的员工时使用额外sql去查询
ofType:指定集合中元素的类型
column:查询的列为员工表的dept_id列,dept_id的值来自于主查询中的id值
<resultMap type="Department" id="baseMapper">
<id property="id" column="id" />
<result property="name" column="name" />
<collection property="emps" ofType="Employee" column="id" select="com.champion.mapper.EmployeeMapper.selectBydeptId"/>
</resultMap>
collection 元素:查询的结果集是一个集合,select返回的是多个对象
在执行额外sql查询时 #{deptId}的值是由主查询中 column的值传入的,是一个值,理论上可以随意取名,最终都是取的传入得值
select * FROM employee WHERE dept_id=#{deptId}
3.多对多保存
保存两个学生和两个老师,且这两个老师都教了这个两个学生。
1.表中关系(通过第三张表将两张表关联起来,并且第三张表不需要主键)
2.Java中的关系
在学生类中维护关系
public class Student {
private Long id;
private String name;
private List<Teacher> teachers=new ArrayList<>();
}
3.根据实体类编写相应的mapper
三个mapper:studentrMapper,TeacherMapper,student_TeacherMapper
在添加数据维护关系时,insert操作在前,java中维护关系在后,并且insert要获取自动生成的主键,将老师信息添加进学生对象的teachers属性中
teacherMapper.insert(t1);…//执行插入操作
s1.getTeachers().add(t1);…//维护Java中的关系
最后维护表中的关系:表 student_teacher
List<Teacher> teachers1 = s1.getTeachers();
List<Teacher> teachers2 = s2.getTeachers();
for (Teacher teacher : teachers1) {
student_TeacherMapper.insert(s2.getId(), teacher.getId());
}
for (Teacher teacher : teachers2) {
student_TeacherMapper.insert(s1.getId(), teacher.getId());
}
4.多对多删除和查询
删除:多对多关系删除操作有时不能直接删除,而是在关系表中将外键列的值置空
查询:查询 id 为 1 的学生,并查询其老师。
使用额外sql查询和内联查询皆可
额外sql:与多对查询相同,sql需要关联第三张表 student_teacher
select *FROM teacher t left join student_teacher st ON t.id=st.teacher_id WHERE st.student_id=#{studentId};