这是学习颜群老师的mybatis教程后整理的笔记
Ⅰ、mybatis介绍与使用
1.mybatis是什么
MyBatis可以简化JDBC操作,实现数据的持久化
-
ORM的概念:Object Relational Mapping对象关系映射,例如person表对应一个person对象
Mybatis是ORM的一个实现。有了mybatis,开发人员像操作对象一样操作数据库表
2.怎么用mybatis
-
用maven
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>x.x.x</version> </dependency>
导入jar包,去官网下载(mybatis-x.x.x.jar)
3.第一次用mybatis操作数据库
构建路径/源路径:java代码在的路径
-
conf.xml:配置数据库信息和需要加载的映射文件,放在源路径下
<?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"> <!--设置环境的id--> <environment id="development"> <transactionManager type="JDBC"/> <!--配置数据源类型--> <dataSource type="POOLED"> <!-- 配置数据库信息 --> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mysqldemo?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <!-- 加载数据库映射文件 --> <mapper resource="com/whl/mapper/personMapper.xml"/> </mappers> </configuration>
- dataSource标签的type属性:配置数据源类型
- UNPOOLED:传统的JDBC模式(每次访问数据库、均需要打开、关闭数据库连接)
- POOLED:使用数据库连接池
- JNDI:从tomcat中获取一个内置的数据库连接池(数据库连接池--数据源)
- environment标签的id属性:设置这个运行环境的id(测试、运行...)
- environments标签的default的属性:填写当前运行需要的环境id值,与environment标签的id属性相对应
- mapper标签的resource属性:数据库映射文件mapper.xml的相对路径
- property标签:用于配置数据库连接字符串、用户名、密码、驱动等信息
- transactionManager标签:用于设置事务方式
- JDBC
- MANAGER
- dataSource标签的type属性:配置数据源类型
-
personMapper.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="com.whl.entity.PersonMapper"> <select id="queryPersonById" resultType="com.whl.entity.Person" parameterType="int"> select * from person where id = #{id} </select> <insert id="addPerson" parameterType="com.whl.entity.Person"> insert into person(id,name,age) values(#{id},#{name},#{age}) </insert> <delete id="deletePersonById" parameterType="int"> delete from person where id = #{id} </delete> <update id="updatePersonById" parameterType="com.whl.entity.Person"> update person set name=#{name},age=#{age} where id=#{id} </update> <select id="queryAllPerson" resultType="com.whl.entity.Person"> select * from person </select> </mapper>
mapper标签的namespace属性:放映射文件的路径,不用xml后缀
-
SQL语句类型的标识符
- select
- insert
- delete
- update
以上四种标签中的id值来标识唯一一条SQL语句
-
实体类Person
package com.whl.entity; public class Person { private int id; private String name; private int age; public Person() { } public Person(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
-
mysql中创建person表如下
-------------------------------------+ | person | CREATE TABLE `person` ( `id` int DEFAULT NULL, `name` varchar(20) DEFAULT NULL, `age` int DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
-
测试类
package com.whl.entity; import java.io.IOException; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class TestMybatis { public static void main(String[] args) throws IOException { // Reader reader=Resources.getResourceAsReader("conf.xml"); SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader); //session -- connection SqlSession session = sqlSessionFactory.openSession(); //通过id值来定位写在mapper中的SQL语句 String statement = "com.whl.entity.PersonMapper.queryPersonById"; //查询一条,用selectOne,第一个参数是sql,第二个参数是sql中的? Person person = session.selectOne(statement,1); System.out.println(person); //关闭数据库连接 session.close(); } }
Ⅱ、基础方式和mapper动态代理方式
1.基础方式
第一个mybatis程序使用的就是基础方式
<select id="queryPersonById" resultType="com.whl.entity.Person" parameterType="int">
select * from person where id = #{id}
</select>
<select id="queryAllPerson" resultType="com.whl.entity.Person">
select * from person
</select>
- 输入参数parameterType和输出参数resultType,在形式上都只能有一个
- 输入参数 :是简单类型(8个基本类型+String)是可以使用任何占位符,#{xxxx};如果是对象类型,则必须是对象的属性#{属性名}
- 输出参数:如果返回值类型是一个 对象(如Student),则无论返回一个、还是多个,在resultType都写成org.lanqiao.entity.Student,即 resultType="org.lanqiao.entity.Student"
- 如果使用的事务方式为jdbc,则需要手工commit提交,即session.commit();(增删改需要提交)
- 所有的标签 <select> <update>等,都必须有sql语句,但是sql参数值可选
2.mapper动态代理方式
约定优于配置
与基础方式的不同点:
约定的目标:省略掉statement,即根据约定直接可以定位出SQL语句
接口:遵循以下规定
方法名和mapper.xml文件中标签的id值相同
方法的输入参数和mapper.xml文件中标签的parameterType类型一致(如果mapper.xml的标签中没有 parameterType,则说明方法没有输入参数)
方法的返回值和mapper.xml文件中标签的 resultType类型一致(无论查询结果是一个 还是多个(student、List<Student>),在mapper.xml标签中的resultType中只写 一个(Student);如果没有resultType,则说明方法的返回值为void)
personMapper.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">
<!--
与基础方式的不同
namespace写的是PersonMapper接口的全类名
-->
<mapper namespace="com.whl.mapper.PersonMapper">
<select id="queryPersonById" resultType="com.whl.entity.Person" parameterType="int">
select * from person where id = #{id}
</select>
<insert id="addPerson" parameterType="com.whl.entity.Person">
insert into person(id,name,age) values(#{id},#{name},#{age})
</insert>
<delete id="deletePersonById" parameterType="int">
delete from person where id = #{id}
</delete>
<update id="updatePersonById" parameterType="com.whl.entity.Person">
update person set name=#{name},age=#{age} where id=#{id}
</update>
<select id="queryAllPerson" resultType="com.whl.entity.Person">
select * from person
</select>
</mapper>
PersonMapper接口
package com.whl.mapper;
import java.util.List;
import com.whl.entity.Person;
//操作mybatis的接口
public interface PersonMapper {
Person queryPersonById(int id);
List<Person> queryAllPerson();
void addPerson(Person person);
void deletePersonById(int id);
void updatePersonById(Person person);
}
测试类
package com.whl.entity;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.javassist.expr.NewArray;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.whl.mapper.PersonMapper;
public class Test {
public static void queryPersonById(int id) throws IOException {
Reader reader=Resources.getResourceAsReader("conf.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
//session -- connection
SqlSession session = sqlSessionFactory.openSession();
PersonMapper personMapper=session.getMapper(PersonMapper.class);
Person person=personMapper.queryPersonById(1);
System.out.println(person);
session.close();
}
public static void queryAllPerson() throws IOException {
Reader reader=Resources.getResourceAsReader("conf.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
PersonMapper personMapper=session.getMapper(PersonMapper.class);
List<Person> persons=personMapper.queryAllPerson();
System.out.println(persons);
session.close();
}
public static void addPerson(Person person) throws IOException {
Reader reader=Resources.getResourceAsReader("conf.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
PersonMapper personMapper=session.getMapper(PersonMapper.class);
personMapper.addPerson(person);
session.commit();
System.out.println("增加成功");
session.close();
}
public static void deletePersonById(int id) throws IOException {
Reader reader=Resources.getResourceAsReader("conf.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
PersonMapper personMapper=session.getMapper(PersonMapper.class);
personMapper.deletePersonById(id);
session.commit();
System.out.println("删除成功");
session.close();
}
public static void updatePersonById(Person person) throws IOException {
Reader reader=Resources.getResourceAsReader("conf.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
PersonMapper personMapper=session.getMapper(PersonMapper.class);
personMapper.updatePersonById(person);
session.commit();
System.out.println("修改成功");
session.close();
}
public static void main(String[] args) throws IOException {
//queryPersonById(1);
//addPerson(new Person(3,"xxx",19));
//updatePersonById(new Person(2,"whl",19));
deletePersonById(3);
queryAllPerson();
}
}
测试类中最重要的代码
StudentMapper studentMapper = session.getMapper(StudentMapper.class) ;
studentMapper.方法();
通过session对象获取接口(session.getMapper(接口.class);),再调用该接口中的方法,程序会自动执行该方法对应的SQL。这样就不需要statement和selectone了
匹配的过程:
- 根据 接口名 找到 mapper.xml文件(根据的是namespace=接口全类名)
- 根据 接口的方法名 找到 mapper.xml文件中的SQL标签 (方法名=SQL标签Id值)
以上2点可以保证: 当我们调用接口中的方法时,程序能自动定位到某一个Mapper.xml文件中的sqL标签
习惯:SQL映射文件(mapper.xml)和接口放在同一个包中(注意修改conf.xml中加载mapper.xml文件的路径)
3.优化
-
可以将配置信息单独放入db.properties文件中,然后再动态引入
形式:kv对
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mysqldemo?serverTimezone=UTC username=root password=123456
db.properties放在源路径
在conf.xml中用properties标签引入
<configuration> <properties resource="db.properties"/> ... </configuration>
通过${key}来取值(类似于EL表达式)
<property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/>
-
MyBatis全局参数,在conf.xml中设置(一般不进行手动设置,使用默认值)
在setting标签中进行设置
<settings> <setting name="cacheEnabled" value="false" /> <setting name="lazyLoadingEnabled" value="false" /> </settings>
-
别名(单个别名、批量别名),在conf.xml中的typeAliases标签进行设置
<typeAliases> <!-- 单个别名 (别名 忽略大小写) --> <!-- <typeAlias type="org.lanqiao.entity.Student" alias="student"/> --> <!-- 批量定义别名 (别名 忽略大小写),以下会自动将该包中的所有类 批量定义别名: 别名就是类名(不带包名,忽略大小写) --> <package name="org.lanqiao.entity"/> </typeAliases>
除了自定义别名外,MyBatis还内置了一些常见类的别名。
Ⅲ、类型处理器
类型处理器:把Java代码中的类型转换成JDBC类型
1.自带类型处理器
MyBatis自带一些常见的类型处理器
2.自定义MyBatis类型处理器
例:
实体类Student : boolean stuSex;
true:男
false:女
表student: number stuSex
1:男
0:女
-
创建转换器
直接实现TypeHandler接口
-
或继承BaseTypeHandler
package org.lanqiao.converter; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; //BaseTypeHandler<java类型> public class BooleanAndIntConverter extends BaseTypeHandler<Boolean>{ //java(boolean)-DB(number) /* * ps:PreparedStatement对象 * i:PreparedStatement对象操作参数的位置 * parameter:java值 * jdbcType:jdbc操作的数据库类型 */ @Override public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType) throws SQLException { if(parameter) { //1 ps.setInt(i, 1); }else { // 0 ps.setInt(i, 0); } } //db(number)->java(boolean) @Override public Boolean getNullableResult(ResultSet rs, String columnName) throws SQLException { int sexNum = rs.getInt(columnName) ;//rs.getInt("stuno") ; return sexNum == 1?true:false ; } @Override public Boolean getNullableResult(ResultSet rs, int columnIndex) throws SQLException { int sexNum = rs.getInt(columnIndex) ;//rs.getInt(1) return sexNum == 1?true:false ; } @Override public Boolean getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { int sexNum = cs.getInt(columnIndex) ;//rs.getInt(1) return sexNum == 1?true:false ; } }
-
配置conf.xml,通过typeHandler标签配置类型转换器
<typeHandlers> <typeHandler handler="org.lanqiao.converter.BooleanAndIntConverter" javaType="Boolean" jdbcType="INTEGER" /> </typeHandlers>
-
配置mapper.xml
<select id="queryStudentByStunoWithConverter" parameterType="int" resultMap="studentResult" ><!--注意这里使用了resultMap而不是resultType--> select * from student where stuno = #{stuno} </select> <!--配置了一些表字段名到属性名的映射--> <resultMap type="student" id="studentResult"> <!-- 分为主键id 和非主键 result--> <id property="stuNo" column="stuno" /> <result property="stuName" column="stuname" /> <result property="stuAge" column="stuage" /> <result property="graName" column="graname" /> <result property="stuSex" column="stusex" javaType="boolean" jdbcType="INTEGER"/> </resultMap>
何时使用resultMap?
- 如果类中属性名和表中的字段名能够合理识别(stuNo -stuno)则可以使用resultType;否则(id-stuno)使用resultMap
- 如果类中属性和表中的字段类型能够合理识别(String-varchar2),则可以使用resultType;否则(boolean-number)使用resultMap
Ⅳ、输入参数
1.简单类型
-
{}、${}的区别
-
{任意值}
${value} ,其中的标识符只能是value
-
{}自动给String类型加上' '(自动类型转换)
${}原样输出,但是适合于动态排序(动态字段)
select stuno,stuname,stuage from student where stuname = #{value} select stuno,stuname,stuage from student where stuname = '${value}' 动态排序: select stuno,stuname,stuage from student order by ${value} asc
-
{}可以防止SQL注入,${}不防止
-
相同之处:都可以获取对象的值(嵌套类型对象)
2.模糊查询获取对象值
- 方式一:用#{}
select stuno,stuname,stuage from student where stuage= #{stuAge} or stuname like #{stuName}
Student student = new Student();
student.setStuAge(24);
student.setStuName("%w%");//在set时就加入了通配符
List<Student> students = studentMapper.queryStudentBystuageOrstuName(student) ;//接口的方法->SQL
-
方式二:用${}
student.setStuName("w");
select stuno,stuname,stuage from student where stuage= #{stuAge} or stuname like '%${stuName}%'
3.输入对象为HashMap
在框架学习中HashMap一般是这样声明的(String,Object对)
Map<String,Object> map = new HashMap<>();
map.put("stuAge",19);
<select id="..." parameterType="HashMap" resultType="...">
select ... where stuage= #{stuAge}<!--通过stuAge取到了map中的值作为参数-->
</select>
Ⅴ、输出参数
输出参数的四种情况:
- 简单类型(8个基本+String)
- 输出参数为实体对象类型
- 输出参数为实体对象类型的集合 :虽然输出类型为集合,但是resultType依然写 集合的元素类型(resyltType="Student")
- 输出参数类型为HashMap
--HashMap本身是一个集合,可以存放多个元素,
但是根据提示发现 返回值为HashMap时 ,查询的结果只能是1个学生(no,name);
-->结论:一个HashMap 对应一个学生的多个元素(多个属性) [一个map,一个学生]
1.resultMap
当实体类的属性、数据表的字段:类型、名字不同时,使用resultMap建立起字段名与属性名的映射
<resultMap type="student" id="queryStudentByIdMap">
<!-- 指定类中的属性和表中的字段对应关系-->
<id property="stuNo" column="id" />
<result property="stuName" column="name" />
</resultMap>
- 用id标签指定主键的映射
- 用result标签指定非主键的映射
- property属性的值为属性名,column属性的值为字段名
2.resultType+HashMap
通过hashmap把属性名与字段名进行对应
- 可用AS取别名的方式进行对应,也可以在select语句字段名后空格给出属性名
<!--注意一定要把返回类型改成hashmap-->
<select id="queryPersonByIdWithHashMap" parameterType="int" resultType="hashmap">
select id as personId,name as personName,age as personAge from person where id=#{id}
</select>
或
<select id="queryPersonByIdWithHashMap" parameterType="int" resultType="hashmap">
select id "personId",name "personName",age "personAge" from person where id=#{id}
</select>
<!--双引号可加可不加-->
HashMap<String, Object> person=personMapper.queryPersonByIdWithHashMap(1);
System.out.println(person);
//{personName=lxl, personAge=19, personId=1}
注意:以hashmap作为返回类型,其中hashmap存储的是某一个学生的多个属性的键值对,而不是多个学生
如何返回多个学生?List<HashMap<String, Object>> persons
List<HashMap<String, Object>> persons = personMapper.queryPersonAllWithHashMap();
System.out.println(persons);
//[{name=lxl, id=1, age=19}, {name=whl, id=2, age=19}]
Ⅵ、动态SQL
1.动态生成where子句
<select id="queryPersonByNameAndAge" parameterType="person" resultMap="person_map">
select id,name,age from person
<where>
<if test="personName!=null and personName!=''">
and name = #{personName}
</if>
<if test="personAge!=null and personAge!=0">
and age = #{personAge}
</if>
</where>
</select>
<resultMap type="person" id="person_map">
<id property="personId" column="id"/>
<result property="personName" column="name"/>
<result property="personAge" column="age"/>
</resultMap>
- where标签:写了后就不用在SQL中手写where关键字了
- if标签
- test属性写条件表达式,为true时执行标签内的SQL
- 注意:条件表达式中写的是对属性名的判断,不同条件用and、or连接
- 第一个if标签中的SQL是可以加and关键字的,因为mybatis会自动忽略这个and关键字
我踩的坑:怎么执行查询都是null,后来发现没有配置resultMap。只要是字段和属性名不一样就必须配置!
2.输入参数为集合或数组
输入参数是list的情况
<!--属性名和字段的映射-->
<resultMap type="person" id="person_map">
<id property="personId" column="id"/>
<result property="personName" column="name"/>
<result property="personAge" column="age"/>
</resultMap>
<select id="queryPersonByNolist" parameterType="list" resultMap="person_map">
select * from person
<where>
<if test="list!=null and list.size>0">
<foreach collection="list" open=" and id in (" close=")" item="personNo" separator=",">
#{personNo}
</foreach>
</if>
</where>
</select>
- 所有集合在mapper中都用list来指代,并不使用集合本身的名称
- foreach标签用于迭代集合
- collection属性:指定要遍历的集合
- open和close:只遍历集合中元素,不遍历集合前后的SQL语句。用这两个属性指定前后的SQL
- item:给集合中每个元素取一个临时的名称。这个名称是无所谓的,只要和标签中#{}的一样就行了
- separator:分隔符,不加就会变成"id in (123)",需要的是"id in (1,2,3)"
- 用#{}来取集合中元素的值
personNos.add(1);
personNos.add(2);
//personNos.add(3);
List<Person> persons=personMapper.queryPersonByNolist(personNos);;
System.out.println(persons);
//[Person [personId=1, personName=lxl, personAge=19], Person [personId=2, personName=whl, personAge=19]]
输入参数是int数组的情况:
<select id="queryPersonByNoarray" parameterType="int[]" resultMap="person_map">
select * from person
<where>
<if test="array!=null and array.length>0">
<foreach collection="array" open=" and id in (" close=")" item="personNo" separator=",">
#{personNo}
</foreach>
</if>
</where>
</select>
- 需要注意集合的大小是size,数组是length
- 传入的简单类型的数组在mapper中一律以array表示
int[] personNos=new int[] {1,2,3};
List<Person> persons=personMapper.queryPersonByNoarray(personNos);;
System.out.println(persons);
//[Person [personId=1, personName=lxl, personAge=19], Person [personId=2, personName=whl, personAge=19], Person [personId=3, personName=abc, personAge=19]]
输入参数是对象数组的情况:
<select id="queryPersonByObjectarray" parameterType="Object[]" resultMap="person_map">
select * from person
<where>
<if test="array!=null and array.length>0">
<foreach collection="array" open=" and id in (" close=")" item="person" separator=",">
#{person.personId}
</foreach>
</if>
</where>
</select>
- 注意:所有类的对象数组的parameterType都是Object[]
- 级联获取对象中的属性值
Person p1=new Person();
Person p2=new Person();
Person p3=new Person();
p1.setPersonId(1);
p2.setPersonId(2);
p3.setPersonId(3);
Person[] persons1=new Person[] {p1,p2,p3};
List<Person> result=personMapper.queryPersonByObjectarray(persons1);;
System.out.println(result);
//[Person [personId=1, personName=lxl, personAge=19], Person [personId=2, personName=whl, personAge=19], Person [personId=3, personName=abc, personAge=19]]
3.提取SQL片段
<sql id="sql1">
select * from person
</sql>
<select id="queryPersonByObjectarray" parameterType="Object[]" resultMap="person_map">
<include refid="sql1"></include>
<where>
<if test="array!=null and array.length>0">
<foreach collection="array" open=" and id in (" close=")" item="person" separator=",">
#{person.personId}
</foreach>
</if>
</where>
</select>
- 用sql标签提取出常用的SQL语句,id标识唯一的SQL语句
- 用include标签中的refid属性指定要使用的SQL语句的id
Ⅶ、关联查询
一对一:association
一对多:collection
1.一对一
方法一:
1.建一个类,继承自属性多的类,再把多出来的属性写在这个新类里
package com.whl.entity;
public class PersonBusiness extends Person{
private int cardId;
private String cardInfo;
public int getCardId() {
return cardId;
}
public void setCardId(int cardId) {
this.cardId = cardId;
}
public String getCardInfo() {
return cardInfo;
}
public void setCardInfo(String cardInfo) {
this.cardInfo = cardInfo;
}
@Override
public String toString() {
return super.toString()+"PersonBusiness [cardId=" + cardId + ", cardInfo=" + cardInfo + "]";
}
}
- 注意tostring方法要重写并调用父类的tostring!
2.设置person表的cardid为外键
3.写SQL
<select id="queryPersonByIdOO" parameterType="int" resultType="PersonBusiness">
select p.*,c.* from person as p inner join personcard as c
on p.cardid=c.cardid
where p.id=#{id}
</select>
- 注意返回类型为PersonBusiness(第三个类)
4.执行
PersonBusiness personBusiness=personMapper.queryPersonByIdOO(1);
System.out.println(personBusiness);
//Person [id=1, name=lxl, age=19]PersonBusiness [cardId=1, cardInfo=lxl info...]
我踩的坑:第一次查询结果只显示了PersonBusiness增加的属性,person中的显示都为null或者0,最后发现是数据库字段名与属性名不一致。修改一致后就可正常显示
方法二:写一个person类和personcard类,在person类中有一个personcard对象
public class PersonCard {
private int cardId;
private String cardInfo;
}
public class Person {
private int id;
private String name;
private int age;
private PersonCard card;//把PersonCard类的对象作为Person类的属性
}
<select id="queryPersonByIdOO" parameterType="int" resultMap="person_card_map">
select p.*,c.* from person as p inner join personcard as c
on p.cardid=c.cardid
where p.id=#{id}
</select>
<resultMap type="person" id="person_card_map">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<association property="card" javaType="PersonCard" >
<id property="cardId" column="cardid"/>
<result property="cardInfo" column="cardinfo"/>
</association>
</resultMap>
- resultMap中的type属性为person,因为person包含了PersonCard
- 一对一关联:用association标签指定一对一
- property属性的值为Person类中PersonCard对象成员的名称
- javaType属性指定了card对象的类名
- id和result标签指定了personcard表与personcard对象字段名、属性名之间的映射关系
Person person=personMapper.queryPersonByIdOO(1);
System.out.println(person);
//Person [id=1, name=lxl, age=19, card=PersonCard [cardId=1, cardInfo=lxl info...]]
2.一对多
1.写xml
<select id="queryPersonByClassId" parameterType="int" resultMap="person_class_map">
select c.*,p.* from person as p
inner join personclass c
on c.classid=p.classid
where c.classid=#{classid}
</select>
<resultMap type="PersonClass" id="person_class_map">
<id property="classId" column="classid"/>
<result property="className" column="classname"/>
<collection property="persons" ofType="Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
</collection>
</resultMap>
- collection标签:指定一对多关联查询
2.写属性类
public class PersonClass {
private int classId;
private String className;
List<Person> persons;
...
}
public class Person {
private int id;
private String name;
private int age;
private PersonCard card;
...
}
- 班级和学生为一对多关系,所以在PersonClass中用List保存学生
- card对象作为Person类的成员
3.测试运行
PersonClass personClass=personMapper.queryPersonByClassId(1);
System.out.println(personClass);
//PersonClass [classId=1, className=c1, persons=[Person [id=1, name=lxl, age=19, card=null]]]
Ⅷ、日志
可以通过日志信息,相信的阅读mybatis执行情况(观察mybatis实际执行sql语句以及SQL中的参数和返回结果)
以log4j为例
引入log4j.jar
-
在conf.xml中开启日志
<settings> <!-- 开启日志,并指定使用的具体日志 --> <setting name="logImpl" value="LOG4J"/> </settings>
如果不指定,Mybatis就会根据以下顺序 寻找日志
SLF4J →Apache Commons Logging →Log4j 2 → Log4j →JDK logging -
编写配置日志输出文件
在src下创建log4j.properties
log4j.rootLogger=DEBUG, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
日志级别:DEBUG<INFO<WARN<ERROR
如果设置为info,则只显示 info及以上级别的信息;
建议:在开发时设置debug,在运行时设置为info或以上。
Ⅸ、延迟加载
也称“懒加载”
什么是延迟加载:
在关联查询(一对一、一对多、多对多)中,如果不采用延迟加载(立即加载),查询时会将一和多都查询,班级、班级中的所有学生。如果想要暂时只查询1的一方,而多的一方先不查询而是在需要的时候再去查询-->延迟加载
-
开启延迟加载conf.xml配置settings
<settings> <!-- 开启延迟加载 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 关闭立即加载 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
-
写接口
List<Person> queryPersonByIdOO();
-
写SQL
<select id="queryPersonByIdOO" resultMap="person_card_map"> select * from person </select> <resultMap type="person" id="person_card_map"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="age" column="age"/> <association property="card" javaType="com.whl.entity.PersonCard" select="com.whl.mapper.PersonCardMapper.queryCardById" column="cardid"> </association> </resultMap>
新建PersonCardMapper,注意要在conf.xml加载该映射!
<mapper namespace="com.whl.mapper.PersonCardMapper"> <select id="queryCardById" parameterType="int" resultType="com.whl.entity.PersonCard"> select * from personcard where cardid = #{cardId} </select> </mapper>
- association标签表示一对一,collection表示一对多
- property表示person类中的card属性
- javaType写card的类名
- select属性写PersonCardMapper中的sql的id。形式:namespace.id
- column关联的外键,作为queryCardById的输入参数
- queryCardById中的resultType属性:写card类的全名
- association标签表示一对一,collection表示一对多
-
测试运行
List<Person> persons=personMapper.queryPersonByIdOO(); System.out.println(persons); for(Person person:persons) { //System.out.println(person.getId()+"----"+person.getName()); PersonCard personCard=person.getCard(); System.out.println(person); } //Person [id=1, name=lxl, age=19, card=PersonCard [cardId=1, cardInfo=lxl info...]] //Person [id=2, name=whl, age=19, card=PersonCard [cardId=2, cardInfo=whl info...]]
Ⅹ、查询缓存
一级缓存 :同一个SqlSession对象
MyBatis默认开启一级缓存,如果用同样的SqlSession对象查询相同的数据,则只会在第一次 查询时 向数据库发送SQL语句,并将查询的结果 放入到SQLSESSION中(作为缓存存在);后续再次查询该同样的对象时,则直接从缓存中查询该对象即可(即省略了数据库的访问)
二级缓存:MyBatis默认情况没有开启二级缓存,需要手工打开。
1.conf.xml
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
2.在具体的mapper.xml中声明开启(studentMapper.xml中)
<mapper namespace="org.lanqiao.mapper.StudentMapper">
<cache/>
<mapper/>
- cache标签:声明此namespace开启二级缓存
根据异常提示:NotSerializableException可知,MyBatis的二级缓存 是将对象 放入硬盘文件中
准备缓存的对象,必须实现了序列化接口(如果开启的缓存Namespace="org.lanqiao.mapper.StudentMapper"),可知序列化对象为Student,因此需要将Student序列化(序列化Student类,以及Student的级联属性、和父类)
- 触发将对象写入二级缓存的时机:SqlSession对象的close()方法。
Mybatis自带二级缓存:[同一个namespace]生成的mapper对象
回顾:namespace的值就是接口的全类名(包名.类名),通过接口可以产生代理对象(studentMapper对象)
namespace决定了studentMapper对象的产生
结论:只要产生的xxxMapper对象 来自于同一个namespace,则 这些对象 共享二级缓存。
注意:二级缓存 的范围是同一个namespace, 如果有多个xxMapper.xml的namespace值相同,则通过这些xxxMapper.xml产生的xxMapper对象仍然共享二级缓存。
- 禁用 :select标签中useCache="false"
清理:
1.与清理一级缓存的方法相同
commit(); (一般执行增删改时 会清理掉缓存;设计的原因 是为了防止脏数据)
在二级缓存中,commit()不能是查询自身的commit。
commit会清理一级和二级缓存;但是 清理二级缓存时,不能是查询自身的commit;
2. 在select标签中 增加属性 flushCache="true"
命中率:缓存中存在就是命中。命中数/查询数
三方提供的二级缓存:ehcache、memcache
要想整合三方提供的二级缓存 (或者自定义二级缓存),必须实现org.apache.ibatis.cache.Cache接口,该接口的默认实现类是PerpetualCache
整合ehcache二级缓存:
- ehcache-core.jar mybatis-Ehcache.jar slf4j-api.jar
- 编写ehcache配置文件 Ehcache.xml
- 开启EhCache二级缓存
在xxxMapper.xml中开启
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<!-- 通过property覆盖Ehcache.xml中的值 -->
<property name="maxElementsInMemory" value="2000"/>
<property name="maxElementsOnDisk" value="3000"/>
</cache>