在开发过程中,最重要的就是在控制台查看程序输出的日志信息,在这里我们选择使用 log4j 工具来输出:
# 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)。
第二条配置语句 “log4j.appender.stdout=org.apache.log4j.ConsoleAppender
” 的含义是,设置名为 stdout 的输出端载体是哪种类型。
第三条配置语句 “log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
” 的含义是,名为 stdout 的输出载体的 layout(即界面布局)是哪种类型。
第四条配置语句 “log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
” 的含义是,如果 layout 界面布局选择了 PatternLayout 灵活布局类型,要指定的打印信息的具体格式。
rn
”,UNIX 平台为 “n
” 在上一篇文章中,我们讲解了一个 MyBatis 的入门程序的开发,了解了 MyBatis 开发的基本内容。今天我们先来了解一下 MyBatis 是如何处理多张数据库表之间的关联关系,其中包括:
首先我们先来建立一个数据模型(删掉之前创建的 student 表):
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 来实现这个一对一查询的实例。
首先创建学生 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 映射文件中定义 类型的查询语句 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="id" property="id"/>
<result column="number" property="number"/>
<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 students;
/* getter and setter */
}
在 Package【pojo】下新建一个【class.xml】文件完成配置:
<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 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】:
"Students" type="pojo.Student">
property="id" column="student_id"/>
"student_name" property="name"/>
<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中的延迟加载](https://blog.csdn.net/eson_15/article/details/51668523)
首先在 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>
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 配置:
<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 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 标签即可:
<cache />
开启二级缓存之后,我们需要为查询结果映射的 POJO 类实现 java.io.serializable
接口,二级缓存可以将内存的数据写到磁盘,存在对象的序列化和反序列化,所以要实现java.io.serializable接口。
我们在同一个 SessionFactory 下查询 id = 1 的数据,只有第一次需要执行 SQL 语句,从后都是从缓存中取出来的:
参考资料:how2j.cn-MyBatis教程、Java3y-Mybatis【缓存、代理、逆向工程】
欢迎转载,转载请注明出处!
@我没有三颗心脏
CSDN博客:http://blog.csdn.net/qq939419061
简书:http://www.jianshu.com/u/a40d61a49221