MyBatis
使用框架的三大步骤
1.导入jar包
2.处理配置文件
3.开发业务处理
MyBatis的配置
MyBatis的依赖
<dependencies> <!--mysqlConnector--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <!--mybatis 核心jar包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <!--lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency> </dependencies>
MyBatis的核心配置文件
<?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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--加载mapper映射文件--> <mappers> <!--这里放的是每个实体类所对应的配置文件--> <mapper resource="com/fjh/mapper/DeptMapper.xml"/> </mappers> </configuration>
MyBatis的初次使用
项目结构
每个实体类所对应的配置文件都必须写在resources下,在resources文件下建立包com.fjh.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"> <mapper namespace="aaa"> <select id="findAll" resultType="com.fjh.pojo.Dept" > select * from dept </select> </mapper>
业务实现
package com.fjh.test; import com.fjh.pojo.Dept; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.List; public class Test1 { @Test public void test(){ //创建回话厂的工人 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //创建sql会话工厂的图纸 InputStream resourceAsStream = null; try { resourceAsStream = Resources.getResourceAsStream("sqlMapContext.xml"); } catch (IOException e) { e.printStackTrace(); } //工人使用图纸建造工厂 SqlSessionFactory factory= builder.build(resourceAsStream); //开启会话 SqlSession sqlSession = factory.openSession(); //执行业务 List<Dept> depts = sqlSession.selectList("findAll"); for (Dept dept : depts) { System.out.println(dept); } } }
MyBatis日志配置
log4j1依赖
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
log4j1配置
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
log4j2依赖
log4j2 <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.12.1</version> </dependency>
log4j2配置
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="DEBUG"> <Appenders> <Console name="Console" target="SYSTEM_ERR"> <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" /> </Console> <RollingFile name="RollingFile" filename="log/test.log" filepattern="${logPath}/%d{YYYYMMddHHmmss}-fargo.log"> <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" /> <Policies> <SizeBasedTriggeringPolicy size="10 MB" /> </Policies> <DefaultRolloverStrategy max="20" /> </RollingFile> </Appenders> <Loggers> <Root level="INFO"> <AppenderRef ref="Console" /> </Root> </Loggers> </Configuration>
SqlSession的三种查询方式
1.单个数据查询
<select id="findOne" resultType="com.fjh.pojo.Emp"> select *from emp where empno = 7521</select>
public void testFindOne(){ Emp emp = sqlSession.selectOne("empMapper.findOne"); System.out.println(emp);}
2.数据集合查找
<select id="findAll" resultType="com.fjh.pojo.Emp"> select * from emp;</select>
@Test public void testFindAll(){ if(sqlSession == null){ System.out.println("sqlSession is null"); } List<Emp> emps = sqlSession.selectList("empMapper.findAll"); emps.forEach(System.out::println); }
3.数据mapper查询
<select id="findMapper" resultType="map"> select *from emp</select>
@Test public void testFindMapper(){ Map<Integer, Emp> empMapper = sqlSession.selectMap("empMapper.findMapper", "EMPNO"); Set<Integer> sets = empMapper.keySet(); for (Integer set : sets) { System.out.println(""+set+": " + empMapper.get(set)); } }
Sqlsession的三种传参方式
在对sql语句中进行参数的传递中,有两种占位符,一种是#{} 一种是${}
#{}相当于原生的jdbc使用用preparedStatement对象
${}相当于原生的JDBC使用Statement对象
一般使用#{}:因为可以防止SQL注入
1.只有一个参数
<!-- 按照员工 工号进行查找员工信息 函数:public Emp findEmpByEmpNo(int empNo) --> <select id="findEmpByEmpNo" resultType="com.fjh.pojo.Emp"> select *from emp where empno = ${empNo} </select>
因为只有一个参数,所以在占位符中所填写的内容就可以随便。但是一般要求见名知意
public void testSingleArg(){ Emp emp = sqlSession.selectOne("findEmpByEmpNo", 7521); System.out.println(emp); }
2参数是mapper集合
<select id="findByDeptNoAndSal" resultType="com.fjh.pojo.Emp" parameterType="map"> select * from emp where deptno = #{deptno} and sal > #{sal} </select>
以mapper作为参数:就是将对应的函数的参数,以键值对的形式存入到mapper集合之中。在占位符中填写对应的参数的键值
@Test public void testMapArg(){ Map<String, Object>map = new HashMap<>(); map.put("deptno",10); map.put("sal",1500.0); List<Emp> emps = sqlSession.selectList("findByDeptNoAndSal", map); emps.forEach(System.out::println); }
3以对象最作为参数
<!-- 查找 查找10号部门中奖金大于30.0的员工信息 函数:public List<Emp> findByDeptNoAndComm --> <select id="findByDeptNoAndComm" resultType="com.fjh.pojo.Emp" parameterType="com.fjh.pojo.Emp"> select *from emp where deptno = #{deptNo} and comm > 30.0 </select>
以对象作为参数,就是将要传入的参数封装为对象,在占位符中要写,对象对应的属性名
@Test public void testObjectArg(){ Emp arg = new Emp(); arg.setDeptNo(10); arg.setComm(30.0); List<Emp> emps = sqlSession.selectList("findByDeptNoAndComm", arg); emps.forEach(System.out::println); }
sqlsession实现CRUD
在sqlsession对象实现insert updata 和 delete时,使用相对应的标签,在这些标签里面没有resultType,默认是int的返回
值。
因为Sqlsession对象在执行增删改的方法的时候默认是使用事务不提交的方式的,所以需要手动调用commit方法就行事务的提交,或者在sqlsessionFactory.OpenSession(ture)设置,事务自动提交
MyBatis基于接口代理开发
1.开发步骤
1.在mapper包下定义对实体类进行数据库相关操作的类,类名的规范写法为实体类的类名+Mapper
2.在resource目录下创建xml文件,用来对mapper包下接口中的方法进行重写,文件名必须和对应的接口名相同(不包含后缀)
3.在对应的点xml中利用标签书写对应方法的sql语句
<mapper namespace="com.fjh.mapper.EmpMapper"> <!-- 查询所有员工信息 List<Emp>findAll(); --> <select id="findAll" resultType="com.fjh.pojo.Emp"> select *from emp ; </select> </mapper>
这里的命名空间必须是对应接口的的全路径 ,标签的id属性必须和接口中的方法名形同。参数的类型属性可以不写,MyBatis会自动补充
2参数问题
1.单个参数
单个参数函数: Emp findByEmpNo( int empNo);功能: 根据员工工号查询员工信息
<!--单个参数函数: Emp findByEmpNo( int empNo);功能: 根据员工工号查询员工信息--> <select id="findByEmpNo" resultType="com.fjh.pojo.Emp"> select * from emp where empNo = #{empNo}; </select>
单个参数的时候可以传入任意的名称,规范编程使用对应方法的形参名
2.多个参数
多个参数 函数: List<Emp>findByDeptNoAndSal(int deptNo,double Sal); 功能: 根据员工的部们号和薪资下限查询员工信息 参数的传入放式: 1.arg* arg0 arg1 arg2 ... 数字表示下标 select * from emp where deptNo = #{arg0} and sal >#{arg1}
2.param* param1 param2 .... 数字表示第几个参数 select * from emp where deptNo = #{param1} and sal >#{param2}
3.使用别名 List<Emp>findByDeptNoAndSal(@Param("deptNo") int deptNo, @Param("sal") double Sal);
<!-- 多个参数 函数: List<Emp>findByDeptNoAndSal(int deptNo,double Sal); 功能: 根据员工的部们号和薪资下限查询员工信息 参数的传入放式: 1.arg* arg0 arg1 arg2 ... 数字表示下标 select * from emp where deptNo = #{arg0} and sal >#{arg1} 2.param* param1 param2 .... 数字表示第几个参数 select * from emp where deptNo = #{param1} and sal >#{param2} 3.使用别名 List<Emp>findByDeptNoAndSal(@Param("deptNo") int deptNo, @Param("sal") double Sal); --> <select id="findByDeptNoAndSal" resultType="emp"> select * from emp where deptNo = #{deptNo} and sal > #{sal} </select>
注意事项:
在使用别名之后,不能使用arg但是可以是用param
3.模糊查询
使用concat函数进行拼接
<!-- 模糊查询 函数: List<Emp>findByEname(String eName); 功能:对员工姓名进行模糊匹配 --> <select id="findByEname" resultType="emp"> select *from emp where eName like concat ('%',#{eName},'%') </select>
public void testFindByEname(){ EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List<Emp> emps = empMapper.findByEname("a"); emps.forEach(System.out::println); }
MyBatis多表查询
手动处理映射关系
当实体类中的属性名和数据库表中的列名不相同,就要进行手动的处理映射关系,否则就会出现不匹配的问题.
在SQL语句的标签的属性中,使用restMap属性
实体类
package com.fjh.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor public class Emp implements Serializable { private Integer empNo; private String name; private String job; private int mgr; private Date heirDate; private Double sal; private Double comm; private Integer deptNo; }
Mapper映射文件
<resultMap id="empResultMap" type="emp"> <result column="ename" property="name"></result> </resultMap> <select id="findAll" resultMap="empResultMap"> select * from emp </select>
column:数据库表中的列名
property:实体类中的属性名
多表查询一对一
业务:依据员工编号查询员工信息和该员工所在的部门信息
sql:select * from emp e left join dept d on e.deptno = d.deptno where empno = 7521
查询结果:
要想在Java代码中输出这个信息,那么必须在emp表中添加dept属性
实体类:
package com.fjh.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor public class Emp implements Serializable { private Integer empNo; private String eName; private String job; private int mgr; private Date heirDate; private Double sal; private Double comm; private Integer deptNo; private Dept dept; }
对应的映射文件
<resultMap id="EmpJoinDeptResultMap" type="emp"> 必须对实体类中和数据库表中字段名相同的字段进行映射 <id column="empNo" property="empNo"></id> <result column="eName" property="eName"></result> <result column="job" property="job"></result> <result column="mgr" property="mgr"></result> <result column="heirDate" property="heirDate"></result> <result column="sal" property="sal"></result> <result column="comm" property="comm"></result> <result column="deptNo" property="deptNo"></result> 用association标签对实体类中的唯一一个其他类对象进行映射 property :数据库表中的字段名 JavaType:实体类中的属性名 <association property="dept" javaType="dept"> <id column="deptNo" property="deptNo"></id> <result column="dName" property="dName"></result> <result column="loc" property="loc"></result> </association> </resultMap> <select id="findEmpJoinDeptByEmpno" resultMap="EmpJoinDeptResultMap"> select * from emp e left join dept d on e.deptno = d.deptno where empno = #{empNo} </select>
多表查询一对多
业务:依据部门号查询该部门的所有员工信息
sql:
select * from dept d left join emp e on d.deptno = e.deptno where d.deptno = 20
查询结果:
实体类:
package com.fjh.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.List; @AllArgsConstructor @Data @NoArgsConstructor public class Dept implements Serializable { private Integer deptNo; private String dName; private String loc; private List<Emp> empList; }
映射文件:
<resultMap id="DeptJoinEmps" type="dept"> <id property="deptNo" column="deptNo"></id> <result property="dName" column="dName"></result> <result property="loc" column="loc"></result> 一对多用collection 属性:ofType 多对应的数据类型 <collection property="empList" ofType="emp"> <id column="empNo" property="empNo"></id> <result column="eName" property="eName"></result> <result column="job" property="job"></result> <result column="mgr" property="mgr"></result> <result column="heirDate" property="heirDate"></result> <result column="sal" property="sal"></result> <result column="comm" property="comm"></result> <result column="deptNo" property="deptNo"></result> </collection> </resultMap> <select id="findDeptJoinEmpByDeptNo" resultMap="DeptJoinEmps">xml
多表查询多对多
业务:根据项目编号查询做该项目的所有员工的信息
SQL:
select * from projects p left join projectrecord pr on p.pid = pr.pid left join emp e on pr.empno = e.empno where p.pid = 2
查询结果
实体类_project
package com.fjh.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @NoArgsConstructor @AllArgsConstructor @Data public class Project { private Integer pid; private String pName; private Integer money; private List<ProjectRecord> projectRecordList; }
实体类_ProjectRecord
package com.fjh.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class ProjectRecord { private Integer empNo; private Integer pid; private Emp emp ; }
映射文件:
<resultMap id="ProjectJoinEmp" type="project"> <id property="pid" column="pid"></id> <result property="pName" column="pName"></result> <result property="money" column="money"></result> <collection property="projectRecordList" ofType="projectRecord"> <id property="pid" column="pid"></id> <id property="empNo" column="empNo"></id> <association property="emp" javaType="emp"> <id column="empNo" property="empNo"></id> <result column="eName" property="eName"></result> <result column="job" property="job"></result> <result column="mgr" property="mgr"></result> <result column="heirDate" property="heirDate"></result> <result column="sal" property="sal"></result> <result column="comm" property="comm"></result> <result column="deptNo" property="deptNo"></result> </association> </collection> </resultMap> <select id="findProjectJoinEmps" resultMap="ProjectJoinEmp"> select * from projects p left join projectrecord pr on p.pid = pr.pid left join emp e on pr.empno = e.empno where p.pid = #{pid} </select>
级联查询积极加载
级联查询映射文件
<mapper namespace="com.fjh.mapper.DeptMapper"> <!-- Dept findDeptJoinEmp(int deptNo);--> <resultMap id="DeptJoinEmp" type="dept"> <id property="deptNo" column="deptNo"></id> <result property="dName" column="dName"></result> <result property="loc" column="loc"></result> <collection property="empList" select="com.fjh.mapper.EmpMapper.findEmpByDeptNo" javaType="list" column="deptNo" jdbcType="INTEGER" fetchType="eager" > </collection> </resultMap> <select id="findDeptJoinEmp" resultMap="DeptJoinEmp"> select * from dept where deptNo = #{deptNo} </select>
缓存
一级缓存
一级缓存是sqlSession中的缓存,当执行一个sql语句后,在sqlSession中就会以键值对的形式查询的数据进行存储,默认是开启的
键是:命名空间+SQL的id+ 参数进过哈希算法得到
值为:查询的结果
因为一级缓存是在sqlSession中的缓存,所有只有在同一个sqlSession对象中适合
如果中间执行了commit方法就会清空缓存
二级缓存
二级缓存是以namespace为标记的缓存,可以是由一个SqlSessionFactory创建的SqlSession之间共享缓存数据。默认并不开启。下面的代码中创建了两个SqlSession,执行相同的SQL语句,尝试让第二个SqlSession使用第一个SqlSession查询后缓存的数据。要求实体类必须实现序列化接口
注意其中的commit(),执行该命令后才会将该SqlSession的查询结果从一级缓存中放入二级缓存,供其他SqlSession使用。另外执行SqlSession的close()也会将该SqlSession的查询结果从一级缓存中放入二级缓存。两种方式区别在当前SqlSession是否关闭了。
执行结果显示进行了两次对数据库的SQL查询,说明二级缓存并没有开启。需要进行如下步骤完成开启。
1) 全局开关:在sqlMapConfig.xml文件中的<settings>标签配置开启二级缓存
<settings>
<setting name="cacheEnabled" value="true"/>
</settings> cacheEnabled的默认值就是true,所以这步的设置可以省略。
2) 分开关:在要开启二级缓存的mapper文件中开启缓存:
<mapper namespace="com.msb.mapper.EmployeeMapper">
<cache/>
</mapper>
3) 二级缓存未必完全使用内存,有可能占用硬盘存储,缓存中存储的JavaBean对象必须实现序列化接口,
public class Emp implements Serializable { }
经过设置后,查询结果如图所示。发现第一个SqlSession会首先去二级缓存中查找,如果不存在,就查询数据库,在commit()或者close()的时候将数据放入到二级缓存。第二个SqlSession执行相同SQL语句查询时就直接从二级缓存中获取了。
注意:
1) MyBatis的二级缓存的缓存介质有多种多样,而并不一定是在内存中,所以需要对JavaBean对象实现序列化接口。
2) 二级缓存是以 namespace 为单位的,不同 namespace 下的操作互不影响
3) 加入Cache元素后,会对相应命名空间所有的select元素查询结果进行缓存,而其中的insert、update、delete在操作是会清空整个namespace的缓存。
4) cache 有一些可选的属性 type, eviction, flushInterval, size, readOnly, blocking。
<cache type="" readOnly="" eviction=""flushInterval=""size=""blocking=""/>
5) 如果在加入Cache元素的前提下让个别select 元素不使用缓存,可以使用useCache属性,设置为false。useCache控制当前sql语句是否启用缓存 flushCache控制当前sql执行一次后是否刷新缓存
<select id="findByEmpno" resultType="emp" useCache="true" flushCache="false">