在开发过程当中,最重要的就是在控制台查看程序输出的日志信息,在这里咱们选择使用 log4j 工具来输出:html
# Global logging configuration # 在开发环境下日志级别要设置成 DEBUG ,生产环境设为 INFO 或 ERROR log4j.rootLogger=DEBUG, stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
其中,第一条配置语句 “log4j.rootLogger=DEBUG, stdout
” 指的是日志输出级别,一共有 7 个级别(OFF、 FATAL、 ERROR、 WARN、 INFO、 DEBUG、 ALL)。java
第二条配置语句 “log4j.appender.stdout=org.apache.log4j.ConsoleAppender
” 的含义是,设置名为 stdout 的输出端载体是哪一种类型。git
第三条配置语句 “log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
” 的含义是,名为 stdout 的输出载体的 layout(即界面布局)是哪一种类型。github
第四条配置语句 “log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
” 的含义是,若是 layout 界面布局选择了 PatternLayout 灵活布局类型,要指定的打印信息的具体格式。web
rn
”,UNIX 平台为 “n
” %d 输出日志时的时间或日期,默认个事为 ISO8601,也能够在其后指定格式,好比 %d{yyy MMM dd HH:mm:ss},输出相似:2018 年 4 月18 日 10:32:00 %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数在上一篇文章中,咱们讲解了一个 MyBatis 的入门程序的开发,了解了 MyBatis 开发的基本内容。今天咱们先来了解一下 MyBatis 是如何处理多张数据库表之间的关联关系,其中包括:spring
首先咱们先来创建一个数据模型(删掉以前建立的 student 表):sql
use mybatis; CREATE TABLE student ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(255) DEFAULT NULL, card_id int(11) NOT NULL, PRIMARY KEY (id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE card ( id int(11) NOT NULL AUTO_INCREMENT, number int(11) NOT NULL, PRIMARY KEY (id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO student VALUES (1,'student1',1); INSERT INTO student VALUES (2,'student2',2); INSERT INTO card VALUES (1,1111); INSERT INTO card VALUES (2,2222);
而后咱们要来确认咱们查询的 SQL 语句,咱们或许能够简单的写成下面这样:数据库
SELECT student.*, card.* FROM student,card WHERE student.card_id = card.id AND card.number = #{value}
肯定了主要的查询 SQL 后,接下来咱们分别使用 resultType 和 resultMap 来实现这个一对一查询的实例。apache
首先建立学生 student 表所对应的 Java 实体类 Student,其中封装的属性信息为响应数据库中的字段:缓存
package pojo; public class Student { int id; String name; int card_id; /* getter and setter */ }
最终咱们执行查询(上述的 SQL 语句)的结果以下:
因为最终的查询的结果是由 resultType 指定的,也就是只能映射一个肯定的 Java 包装类,上面的 Stuent 类只包含了学生的基本信息,并无包含 Card 的信息,因此咱们要建立一个最终映射类,以 Student 类为父类,而后追加 Card 的信息:
package pojo; public class StudentAndCard extends Student { private int number; /* getter and setter /* }
而后在 Student.xml 映射文件中定义 <select>
类型的查询语句 SQL 配置,将以前设计好的 SQL 语句配置进去,而后指定输出参数属性为 resultType,类型为 StudentAndCard 这个 Java 包装类:
<select id="findStudentByCard" parameterType="_int" resultType="Student"> SELECT student.*, card.* FROM student,card WHERE student.card_id = card.id AND card.number = #{value} </select>
而后在测试类中编写测试方法:
@Test public void test() throws IOException { // 根据 mybatis-config.xml 配置的信息获得 sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 而后根据 sqlSessionFactory 获得 session SqlSession session = sqlSessionFactory.openSession(); // 找到身份证身份证号码为 1111 的学生 StudentAndCard student = session.selectOne("findStudentByCard",1111); // 得到其姓名并输出 System.out.println(student.getName()); }
得到正确结果:
使用 resultMap 能够将数据字段映射到名称不同的响应实体类属性上,重要的是,能够映射实体类中包裹的其余实体类。
首先咱们来建立一个封装了 Card 号码和 Student 实体类的 StudentWithCard 类:
package pojo; public class StudentWithCard { Student student; int number; int id; /* getter and setter */ }
SQL 语句依然没有变化,可是使用的输出映射属性改成了 resultMap ,其中的映射类型是 id 为 StudentInfoMap 的 resultMap 配置:
<select id="findStudentByCard" parameterType="_int" resultMap="StudentInfoMap"> SELECT student.*, card.* FROM student,card WHERE student.card_id = card.id AND card.number = #{value} </select> <resultMap id="StudentInfoMap" type="pojo.StudentWithCard"> <!-- id 标签表示对应的主键 column 对应查询结果的列值 property 对应封装类中的属性名称 --> <id column="id" property="id"/> <result column="number" property="number"/> <!-- association 表示关联的嵌套结果, 能够简单理解就是为封装类指定的标签 --> <association property="student" javaType="pojo.Student"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="card_id" property="card_id"/> </association> </resultMap>
稍微修改一下测试类,测试使用 resultMap 实现的一对一查询映射:
@Test public void test() throws IOException { // 根据 mybatis-config.xml 配置的信息获得 sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 而后根据 sqlSessionFactory 获得 session SqlSession session = sqlSessionFactory.openSession(); // 找到身份证身份证号码为 1111 的学生 StudentWithCard student = session.selectOne("findStudentByCard", 1111); // 得到其姓名并输出 System.out.println(student.getStudent().getName()); }
测试仍然能获得正确的结果:
仍是先来创建数据模型,删掉以前的:
use mybatis; CREATE TABLE student ( student_id int(11) NOT NULL AUTO_INCREMENT, name varchar(255) DEFAULT NULL, PRIMARY KEY (student_id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE class ( class_id int(11) NOT NULL AUTO_INCREMENT, name varchar(255) NOT NULL, student_id int(11) NOT NULL, PRIMARY KEY (class_id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO student VALUES (1,'student1'); INSERT INTO student VALUES (2,'student2'); INSERT INTO class VALUES (1,'Java课',1); INSERT INTO class VALUES (2,'Java课',2);
name
字段表示课程的名称。而后咱们来编写咱们的 SQL 语句:
SELECT student.* FROM student, class WHERE student.student_id = class.student_id AND class.class_id = #{value}
咱们执行的结果以下:
咱们再来建立对应的实体类:
public class Student { private int id; private String name; /* getter and setter */ } public class Class { private int id; private String name; private List<Student> students; /* getter and setter */ }
在 Package【pojo】下新建一个【class.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="class"> <resultMap id="Students" type="pojo.Student"> <id column="student_id" property="id"/> <result column="name" property="name"/> </resultMap> <select id="listStudentByClassName" parameterType="String" resultMap="Students"> SELECT student.* FROM student, class WHERE student.student_id = class.student_id AND class.name= #{value} </select> </mapper>
编写测试类:
@Test public void test() throws IOException { // 根据 mybatis-config.xml 配置的信息获得 sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 而后根据 sqlSessionFactory 获得 session SqlSession session = sqlSessionFactory.openSession(); // 查询上Java课的所有学生 List<Student> students = session.selectList("listStudentByClassName", "Java课"); for (Student student : students) { System.out.println("ID:" + student.getId() + ",NAME:" + student.getName()); } }
运行测试结果,成功:
创建数据模型:
use mybatis; CREATE TABLE students ( student_id int(11) NOT NULL AUTO_INCREMENT, student_name varchar(255) DEFAULT NULL, PRIMARY KEY (student_id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE courses ( course_id int(11) NOT NULL AUTO_INCREMENT, course_name varchar(255) NOT NULL, PRIMARY KEY (course_id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE student_select_course( s_id int(11) NOT NULL, c_id int(11) NOT NULL, PRIMARY KEY(s_id,c_id) ) DEFAULT CHARSET=utf8; INSERT INTO students VALUES (1,'student1'); INSERT INTO students VALUES (2,'student2'); INSERT INTO courses VALUES (1,'Java课'); INSERT INTO courses VALUES (2,'Java Web课'); INSERT INTO student_select_course VALUES(1,1); INSERT INTO student_select_course VALUES(1,2); INSERT INTO student_select_course VALUES(2,1); INSERT INTO student_select_course VALUES(2,2);
根据要求咱们来设计一下 SQL 语言:
SELECT s.student_id,s.student_name FROM students s,student_select_course ssc,courses c WHERE s.student_id = ssc.s_id AND ssc.c_id = c.course_id AND c.course_name = #{value}
执行 SQL 结果以下:
实体类雷同,就再也不赘述,咱们直接来配置映射文件【Student.xml】:
<resultMap id="Students" type="pojo.Student"> <id property="id" column="student_id"/> <result column="student_name" property="name"/> </resultMap> <select id="findStudentsByCourseName" parameterType="String" resultMap="Students"> SELECT s.student_id,s.student_name FROM students s,student_select_course ssc,courses c WHERE s.student_id = ssc.s_id AND ssc.c_id = c.course_id AND c.course_name = #{value} </select>
测试类也雷同,只须要修改一下调用的 id (改成findStudentsByCourseName)就行了,直接上测试结果:
相反也是同样的,重要的是 SQL 语句和映射。
什么是延迟加载?从字面上理解,就是对某一类信息的加载以前须要延迟一下子。在 MyBatis 中,一般会进行多表联合查询,可是有的时候不会当即用到全部的联合查询结果,这时候就能够采用延迟加载的功能。
关联查询: SELECT orders.*, user.username FROM orders, user WHERE orders.user_id = user.id 延迟加载至关于: SELECT orders.*, (SELECT username FROM USER WHERE orders.user_id = user.id) username FROM orders
因此这就比较直观了,也就是说,我把关联查询分两次来作,而不是一次性查出全部的。第一步只查询单表orders,必然会查出orders中的一个user_id字段,而后我再根据这个user_id查user表,也是单表查询。
参考文章:[ 【MyBatis学习11】MyBatis中的延迟加载 ](http://www.javashuo.com/article/p-qlkugbfa-ca.html)
首先在 Mapper 映射文件中定义只查询全部订单信息的 SQL :
<select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap"> SELECT * FROM orders </select>
上面的 SQL 语句查询全部的订单信息,而每一个订单信息中会关联查询用户,但因为但愿延迟加载用户信息,因此会在 id 为 "OrdersUserLazyLoadingResultMap
" 的 resultMap 对应的结果集配置中进行配置:
最后配置延迟加载要执行的获取用户信息的 SQL:
<select id="findUserById" parameterType="int" resultType="user"> select * from user where id = #{id} </select>
上面的配置会被用来延迟加载的 resultMap 中的 association 调用,输入参数就是 association 中 column 中定义的字段信息。
在编写测试方法以前,首先须要开启延迟加载功能(这在 MyBatis 中默认是禁用掉的)。这须要在 MyBatis 的全局配置文件 mybatis-config.xml 中配置 setting 属性,将延迟加载(lazyLoadingEnable)的开关设置成 “ture
” ,而且因为是按需加载,因此还须要将积极加载改成消极加载:
<settings> <!-- 打开延迟加载的开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 将积极加载改成消极加载,即延迟加载 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
什么是 Mapper 动态代理?通常建立 Web 工程时,从数据库取数据的逻辑会放置在 DAO 层(Date Access Object,数据访问对象)。使用 MyBatis 开发 Web 工程时,经过 Mapper 动态代理机制,能够只编写数据交互的接口及方法定义,和对应的 Mapper 映射文件,具体的交互方法实现由 MyBatis 来完成。这样大大节省了开发 DAO 层的时间。
实现 Mapper 代理的方法并不难,只须要遵循必定的开发规范便可。
咱们编写一个使用 Mapper 代理查询学生信息的示例,首先仍是在【pojo】下新建一个名为 StudentMapper.xml 的 Mapper 配置文件,其中包含了对 Student 的增删改查的 SQL 配置:
<?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="mapper.StudentMapper"> <!-- 查询学生 --> <select id="findStudentById" parameterType="_int" resultType="pojo.Student"> SELECT * FROM student WHERE student_id = #{id} </select> <!-- 增长用户 --> <insert id="insertStudent" parameterType="pojo.Student"> INSERT INTO student(student_id, name) VALUES(#{id}, #{name}) </insert> <!-- 删除用户 --> <delete id="deleteStudent" parameterType="_int"> DELETE FROM student WHERE student_id = #{id} </delete> <!-- 修改用户 --> <update id="updateStudent" parameterType="pojo.Student"> UPDATE student SET name = #{name} WHERE student_id = #{id} </update> </mapper>
若是须要使用 StudentMapper.xml 的 Mapper 代理,首先须要定义一个接口,名为 StudentMapper。而后在里面新建四个方法定义,分别对应 StudentMapper.xml 中的 Student 的增删改查的 SQL 配置,而后将 StudentMapper 中的 namespace 改成 StudentMapper 接口定义的地方(也就是 mapper 包下的 StudentMapper),这样就能够在业务类中使用 Mapper 代理了,接口代码以下:
package mapper; import pojo.Student; public interface StudentMapper { // 根据 id 查询学生信息 public Student findStudentById(int id) throws Exception; // 添加学生信息 public void insertStudent(Student student) throws Exception; // 删除学生信息 public void deleteStudent(int id) throws Exception; // 修改学生信息 public void updateStudent(Student student) throws Exception; }
在测试方法中,使用 SqlSession 类的 getMapper 方法,并将要加载的 Mapper 代理的接口类传递进去,就能够得到相关的 Mapper 代理对象,使用 Mapper 代理对象去对学生信息进行增删改查:
@Test public void test() throws Exception { // 根据 mybatis-config.xml 配置的信息获得 sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 而后根据 sqlSessionFactory 获得 session SqlSession session = sqlSessionFactory.openSession(); // 获取 Mapper 代理 StudentMapper studentMapper = session.getMapper(StudentMapper.class); // 执行 Mapper 代理独享的查询方法 Student student = studentMapper.findStudentById(1); System.out.println("学生的姓名为:" + student.getName()); session.close(); }
运行测试方法,看到正确的结果:
使用 Mapper 代理可让开发更加简洁,使查询结构更加清晰,工程结构更加规范。
在上面的例子中,咱们已经有了方便的 Mapper 代理对象,咱们能够进一步省掉 XML 的配置信息,进而使用方便的注解来开发 MyBatis ,让咱们实际来操练一下:
咱们把 StudentMapper.xml 下配置的 SQL 语句经过注解的方式原封不动的配置在 StudentMapper 接口中:
public interface StudentMapper { // 根据 id 查询学生信息 @Select("SELECT * FROM student WHERE student_id = #{id}") public Student findStudentById(int id) throws Exception; // 添加学生信息 @Insert("INSERT INTO student(student_id, name) VALUES(#{id}, #{name})") public void insertStudent(Student student) throws Exception; // 删除学生信息 @Delete("DELETE FROM student WHERE student_id = #{id}") public void deleteStudent(int id) throws Exception; // 修改学生信息 @Update("UPDATE student SET name = #{name} WHERE student_id = #{id}") public void updateStudent(Student student) throws Exception; }
将以前配置的映射注释掉,新建一条:
<!-- 映射文件 --> <mappers> <!--<mapper resource="pojo/StudentMapper.xml"/>--> <mapper class="mapper.StudentMapper"/> </mappers>
resource
属性),而是类(使用 class
属性)上面的测试代码不用修改,直接运行,也能获得正确结果:
更多的注解:戳这里
在 Web 系统中,最重要的操做就是查询数据库中的数据。可是有些时候查询数据的频率很是高,这是很耗费数据库资源的,每每会致使数据库查询效率极低,影响客户的操做体验。因而咱们能够将一些变更不大且访问频率高的数据,放置在一个缓存容器中,用户下一次查询时就从缓存容器中获取结果。
一级查询存在于每个 SqlSession 类的实例对象中,当第一次查询某一个数据时,SqlSession 类的实例对象会将该数据存入一级缓存区域,在没有收到改变该数据的请求以前,用户再次查询该数据,都会从缓存中获取该数据,而不是再次链接数据库进行查询。
第一次发出一个查询 sql,sql 查询结果写入 sqlsession 的一级缓存中,缓存使用的数据结构是一个 map
同一个 sqlsession 再次发出相同的 sql,就从缓存中取不走数据库。若是两次中间出现 commit 操做(修改、添加、删除),本 sqlsession 中的一级缓存区域所有清空,下次再去缓存中查询不到因此要从数据库查询,从数据库查询到再写入缓存。
public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session1 = sqlSessionFactory.openSession(); Category c1 = session1.selectOne("getCategory", 1); System.out.println(c1); Category c2 = session1.selectOne("getCategory", 1); System.out.println(c2); session1.commit(); session1.close(); }
运行,能够看到第一次会去数据库中取数据,可是第二次就不会访问数据库了,而是直接从session中取出来:
public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session1 = sqlSessionFactory.openSession(); Category c1 = session1.selectOne("getCategory", 1); System.out.println(c1); Category c2 = session1.selectOne("getCategory", 1); System.out.println(c2); session1.commit(); session1.close(); SqlSession session2 = sqlSessionFactory.openSession(); Category c3 = session2.selectOne("getCategory", 1); System.out.println(c3); session2.commit(); session2.close(); }
这一次,另外打开一个 session , 取一样 id 的数据,就会发现须要执行 sql 语句,证明了一级缓存是在 session 里的:
MyBatis 一级缓存值得注意的地方:
问题: 有些时候,在 Web 工程中会将执行查询操做的方法封装在某个 Service 方法中,当查询完一次后,Service 方法结束,此时 SqlSession 类的实例对象就会关闭,一级缓存就会被清空。
二级缓存原理:
二级缓存的范围是 mapper 级别(mapper即同一个命名空间),mapper 以命名空间为单位建立缓存数据结构,结构是 map。
要开启二级缓存,须要进行两步操做。
**第一步:**在 MyBatis 的全局配置文件 mybatis-config.xml 中配置 setting 属性,设置名为 “cacheEnable
” 的属性值为 “true
” 便可:
<settings> <!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/> </settings>
**第二步:**而后因为二级缓存是 Mapper 级别的,还要在须要开启二级缓存的具体 mapper.xml 文件中开启二级缓存,只须要在相应的 mapper.xml 中添加一个 cache 标签便可:
<!-- 开启本 Mapper 的 namespace 下的二级缓存 --> <cache />
开启二级缓存以后,咱们须要为查询结果映射的 POJO 类实现 java.io.serializable
接口,二级缓存能够将内存的数据写到磁盘,存在对象的序列化和反序列化,因此要实现java.io.serializable接口。
咱们在同一个 SessionFactory 下查询 id = 1 的数据,只有第一次须要执行 SQL 语句,从后都是从缓存中取出来的:
欢迎转载,转载请注明出处! 简书ID:@我没有三颗心脏 github:wmyskxz 欢迎关注公众微信号:wmyskxz_javaweb 分享本身的Java Web学习之路以及各类Java学习资料