4 关联映射
一对一 人 +IdCard
public class Student {
private Integer id;
private String name;
private Card card;//关联属性
StudentMapper.xml
<mapper namespace="studentNamespace">
<resultMap type="cn.atcast.javaee.mybatis.one2one.Student" id="studentMap">
<id property="id" column="sid"></id>
<result property="name" column="sname"></result>
<!-- association :关联 -->
<association property="card" resultMap="cardNamespace.cardMap"></association>
</resultMap>
<!-- 查询1号学生的信息 -->
<select id="findById" parameterType="int" resultMap="studentMap">
select s.sid,s.sname,c.cid,c.cnum
from students s INNER JOIN cards c
on s.scid=c.cid
where s.sid=#{id}
</select>
<!-- 按学生名查询 -->
<select id="findByName" parameterType="string" resultMap="studentMap">
select s.sid,s.sname,c.cid,c.cnum
from students s INNER JOIN cards c
on s.scid=c.cid
where s.sname=#{name}
</select>
</mapper>
public class Card {
private Integer id;
private String num;
private Student student; //双向
CardMapper.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="cardNamespace">
<resultMap type="cn.atcast.javaee.mybatis.one2one.Card" id="cardMap">
<id property="id" column="cid"/>
<result property="num" column="cnum"></result>
</resultMap>
</mapper>
/**
*工具类MybatisUtil
*/
首先回顾一下我们使用Mybatis来操作数据库的步骤:
1、加载核心配置文件sqlMapConfig.xml(名字可以自己随便取);
2、通过 SqlSessionFactoryBuilder来得到SqlSessionFactory ;
3、通过SqlSessionFactory 来创建SqlSession;
4、通过SqlSession来调用增删该查的方法或者获取Mapper对象来调用相应的方法去操作数据库;
5、提交事务;
6、关闭SqlSession。
但是每次操作数据库都要重复上述步骤,为什么不像封装jdbcUtil一样封装一个工具类呢?
import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
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 MybatisUtil {
// 将SqlSession创建出来,存放到Threadlocal中,需要时再从中取出
private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();
private static SqlSessionFactory sqlSessionFactory;
/**
* 加载位于src/mybatis.xml配置文件
*/
static{
try {
// InputStream 对象接收也可以
Reader reader = Resources.getResourceAsReader("mybatis.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 禁止外界通过new方法创建
*初始化sqlSessionFactory,只需要一个对象,所以是静态的
*/
private MybatisUtil(){}
/**
* 获取SqlSession
*/
public static SqlSession getSqlSession(){
//从当前线程中获取SqlSession对象
SqlSession sqlSession = threadLocal.get();
//如果SqlSession对象为空
if(sqlSession == null){
//在SqlSessionFactory非空的情况下,获取SqlSession对象
sqlSession = sqlSessionFactory.openSession();
//将SqlSession对象与当前线程绑定在一起
threadLocal.set(sqlSession);
}
//返回SqlSession对象
return sqlSession;
}
/**
* 关闭SqlSession与当前线程分开
*/
public static void closeSqlSession(){
//从当前线程中获取SqlSession对象
SqlSession sqlSession = threadLocal.get();
//如果SqlSession对象非空
if(sqlSession != null){
//关闭SqlSession对象
sqlSession.close();
//分开当前线程与SqlSession对象的关系,目的是让GC尽早回收
threadLocal.remove();//threadLocal.set(null);
}
}
/**
* 测试
*/
public static void main(String[] args) {
Connection conn = MybatisUtil.getSqlSession().getConnection();
System.out.println(conn!=null?"连接成功":"连接失败");
}
}
测试类
public class StudentCardDao {
/**
* 查询1号学生的信息
* @param id 表示学生的编号
*/
public Student findById(int id) throws Exception{
SqlSession sqlSession = null;
try{
sqlSession = MybatisUtil.getSqlSession();
return sqlSession.selectOne("studentNamespace.findById",id);
}catch(Exception e){
e.printStackTrace();
throw e;
}finally{
MybatisUtil.closeSqlSession();
}
}
/**
* 查询"哈哈"学生的信息
* @param name 表示学生的姓名
*/
public Student findByName(String name) throws Exception{
SqlSession sqlSession = null;
try{
sqlSession = MybatisUtil.getSqlSession();
return sqlSession.selectOne("studentNamespace.findByName",name);
}catch(Exception e){
e.printStackTrace();
throw e;
}finally{
MybatisUtil.closeSqlSession();
}
}
public static void main(String[] args) throws Exception{
StudentCardDao dao = new StudentCardDao();
Student s = dao.findById(1);
System.out.println(s.getId()+":"+s.getName()+":"+s.getCard().getId()+":"+s.getCard().getNum());
System.out.println("-------------------------------");
s = dao.findByName("哈哈");
System.out.println(s.getName()+"的身份证号码为:" + s.getCard().getNum());
}
}
4.2 一对多映射【班级与学生】
/**
- 学科(单方)
*/
public class Grade {
private Integer id;
private String name;
//学科就是系
private List<Student> studentList = new ArrayList<Student>();//关联属性
public Grade(){}
..set/get省略
}
GradeMapper.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="gradeNamespace">
<resultMap type="cn.atcast.javaee.mybatis.one2many.Grade" id="gradeMap">
<id property="id" column="gid"></id>
<result property="name" column="gname"></result>
<!-- <collection property="studentList" resultMap="studentNamespace.studentMap"></collection> 不写也可以拿到-->
</resultMap>
<!-- 根据用户名去查班级 -->
<select id="findByName" parameterType="string" resultMap="gradeMap">
select g.gname
from students s INNER JOIN grades g
on s.sgid=g.gid
and s.sname=#{name}
</select>
</mapper>
/**
- 学生(多方)
*/
public class Student {
private Integer id;
private String name;
private Grade grade;//关联属性
public Student(){}
..set/get省略
}
StudentMapper.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="studentNamespace">
<resultMap type="cn.atcast.javaee.mybatis.one2many.Student" id="studentMap">
<id property="id" column="sid"></id>
<result property="name" column="sname"></result>
<association property="grade" resultMap="gradeNamespace.gradeMap"></association>
</resultMap>
<select id="findAllByName" parameterType="string" resultMap="studentMap">
select s.sid,s.sname
from students s INNER JOIN grades g
on s.sgid=g.gid
and g.gname=#{name}
</select>
</mapper>
/** 测试
- {持久层}
*/
public class GradeStudentDao {
/**
* 查询java学科有哪些学生信息
* @param name 表示学科名
*/
public List<Student> findAllByName(String name) throws Exception{
SqlSession sqlSession = null;
try{
sqlSession = MybatisUtil.getSqlSession();
return sqlSession.selectList("studentNamespace.findAllByName",name);
}catch(Exception e){
e.printStackTrace();
throw e;
}finally{
MybatisUtil.closeSqlSession();
}
}
/**
* 查询哈哈是哪个学科的
* @param name 表示学生姓名
*/
public Grade findByName(String name) throws Exception{
SqlSession sqlSession = null;
try{
sqlSession = MybatisUtil.getSqlSession();
return sqlSession.selectOne("gradeNamespace.findByName",name);
}catch(Exception e){
e.printStackTrace();
throw e;
}finally{
MybatisUtil.closeSqlSession();
}
}
public static void main(String[] args) throws Exception{
GradeStudentDao dao = new GradeStudentDao();
List<Student> studentList = dao.findAllByName("java");
System.out.println("java学科有"+studentList.size()+"个学生,它们信息如下:");
for(Student s : studentList){
System.out.println(s.getId()+":"+s.getName());
}
System.out.println("-----------------------------------------------------------");
Grade grade = dao.findByName("哈哈");
System.out.println("哈哈是"+grade.getName()+"学科的");
System.out.println("-----------------------------------------------------------");
grade = dao.findByName("呵呵");
System.out.println("呵呵是"+grade.getName()+"学科的");
}
}
4.3 多对多映射【学生与课程】
/**
- 学生(多方)
*/
public class Student {
private Integer id;
private String name;
private List<Course> courseList = new ArrayList<Course>();//关联属性
public Student(){}
StudentMapper.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="studentNamespace">
<resultMap type="cn.atcast.javaee.mybatis.many2many.Student" id="studentMap">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
</resultMap>
<select id="findAllByCourseName" parameterType="string" resultMap="studentMap">
select s.sname
from students s inner join middles m
on s.sid = m.msid
inner join courses c
on m.mcid = c.cid
and c.cname = #{name}
</select>
</mapper>
/**
- 课程(多方)
*/
public class Course {
private Integer id;
private String name;
private List<Student> studentList = new ArrayList<Student>();//关联属性
public Course(){}
CourseMapper.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="courseNamespace">
<resultMap type="cn.atcast.javaee.mybatis.many2many.Course" id="courseMap">
<id property="id" column="cid"/>
<result property="name" column="cname"/>
</resultMap>
<!-- 查询哈哈选学了哪些课程 -->
<select id="findAllByName" parameterType="string" resultMap="courseMap">
select c.cid,c.cname
from students s inner join middles m
on s.sid = m.msid
inner join courses c
on m.mcid = c.cid
and s.sname = #{name}
</select>
</mapper>
StudentCourseDao.java
public class StudentCourseDao {
/**
* 查询哈哈选学了哪些课程
* @param name 表示学生的姓名
*/
public List<Course> findAllByName(String name) throws Exception{
SqlSession sqlSession = null;
try{
sqlSession = MybatisUtil.getSqlSession();
return sqlSession.selectList("courseNamespace.findAllByName",name);
}catch(Exception e){
e.printStackTrace();
throw e;
}finally{
MybatisUtil.closeSqlSession();
}
}
/**
* 查询java课程有哪些学生选修
* @param name 表示学生的课程
*/
public List<Student> findAllByCourseName(String name) throws Exception{
SqlSession sqlSession = null;
try{
sqlSession = MybatisUtil.getSqlSession();
return sqlSession.selectList("studentNamespace.findAllByCourseName",name);
}catch(Exception e){
e.printStackTrace();
throw e;
}finally{
MybatisUtil.closeSqlSession();
}
}
public static void main(String[] args) throws Exception{
StudentCourseDao dao = new StudentCourseDao();
List<Course> courseList = dao.findAllByName("哈哈");
System.out.print("哈哈选学了" + courseList.size()+"个课程,分别是:");
for(Course c : courseList){
System.out.print(c.getName()+" ");
}
System.out.println("\n---------------------");
List<Student> studentList = dao.findAllByCourseName("android");
System.out.println("选修了android课程的学生有"+studentList.size()+"个,分别是:");
for(Student s : studentList){
System.out.print(s.getName()+" ");
}
}
}
6 Mybatis逆向工程
使用官方网站的mapper自动生成工具mybatis-generator-core-1.3.2来生成po类和mapper映射文件。
作用:mybatis官方提供逆向工程,可以使用它通过数据库中的表来自动生成Mapper接口和映射文件(单表增删改查)和Po类.
导入的jar包有:
6.1 第一步:mapper生成配置文件:
在generatorConfig.xml中配置mapper生成的详细信息,注意改下几点:
1、 添加要生成的数据库表
2、 po文件所在包路径
3、 mapper文件所在包路径
配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="caigouTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="root">
</jdbcConnection>
<!--
<jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:orcl"
userId="scott"
password="tiger">
</jdbcConnection>
-->
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer true,把JDBC DECIMAL 和
NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="cn.atcast.pojo"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetPackage:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="cn.atcast.mapper"
targetProject=".\src">
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口的生成位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cn.atcast.mapper"
targetProject=".\src">
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定表 -->
<table schema="" tableName="user" />
<table schema="" tableName="orders" />
</context>
</generatorConfiguration>
6.2 第二步:使用java类生成mapper文件:
GeneratorSqlmap.java
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.DefaultShellCallback;
public class GeneratorSqlmap {
public void generator() throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("generatorConfig-base.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
public static void main(String[] args) throws Exception {
try {
GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}
}
}
6.3 第三步:拷贝生成的mapper文件到工程中指定目录中
6.3.1 Mapper.xml
Mapper.xml的文件拷贝至mapper目录内
6.3.2 Mapper.java
Mapper.java的文件拷贝至mapper 目录内
注意:mapper xml文件和mapper.java文件在一个目录内且文件名相同。
6.4 第四步Mapper接口测试
学会使用mapper自动生成的增、删、改、查方法。
cn.atcast.mapper/UserMapper.java
package cn.atcast.test;
import java.io.InputStream;
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.Before;
import org.junit.Test;
import cn.atcast.mapper.UserMapper;
import cn.atcast.pojo.User;
public class UserMapperTest {
private SqlSessionFactory factory;
// 作用:在测试方法前执行这个方法
@Before
public void setUp() throws Exception {
String resource = "SqlMapConfig.xml";
// 通过流将核心配置文件读取进来
InputStream inputStream = Resources.getResourceAsStream(resource);
// 通过核心配置文件输入流来创建会话工厂
factory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() throws Exception {
SqlSession openSession = factory.openSession();
UserMapper mapper = openSession.getMapper(UserMapper.class);
User user=mapper.selectByPrimaryKey(1);
System.out.println(user);
}
}
6.5 逆向工程注意事项
6.5.1 Mapper文件内容不覆盖而是追加
XXXMapper.xml文件已经存在时,如果进行重新生成则mapper.xml文件内容不被覆盖而是进行内容追加,结果导致mybatis解析失败。
解决方法:删除原来已经生成的mapper xml文件再进行生成。
Mybatis自动生成的po及mapper.java文件不是内容而是直接覆盖没有此问题。
7 Hibernate与Mybatis对比
首先简单介绍下两者的概念
Hibernate :Hibernate 是当前最流行的ORM框架,对数据库结构提供了较为完整的封装。
Mybatis:Mybatis同样也是非常流行的ORM框架,主要着力点在于POJO 与SQL之间的映射关系。
其次具体从几个方面说一下两者的区别:
1.两者最大的区别
针对简单逻辑,Hibernate和MyBatis都有相应的代码生成工具,可以生成简单基本的DAO层方法。
针对高级查询,Mybatis需要手动编写SQL语句,以及ResultMap。而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注于业务流程。
2.开发难度对比
Hibernate的开发难度要大于Mybatis。主要由于Hibernate比较复杂、庞大,学习周期较长。
而Mybatis则相对简单一些,并且Mybatis主要依赖于sql的书写,让开发者感觉更熟悉。
3.sql书写比较
Mybatis的SQL是手动编写的,所以可以按需求指定查询的字段。不过没有自己的日志统计,所以要借助log4j来记录日志。
Hibernate也可以自己写SQL来指定需要查询的字段,但这样就破坏了Hibernate开发的简洁性。不过Hibernate具有自己的日志统计。
4.数据库扩展性比较
Mybatis由于所有SQL都是依赖数据库书写的,所以扩展性,迁移性比较差。
Hibernate与数据库具体的关联都在XML中,所以HQL对具体是用什么数据库并不是很关心。
5.缓存机制比较
相同点:Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓存方案,创建适配器来完全覆盖缓存行为。
不同点:Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。
MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。
两者比较:因为Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。
而MyBatis在这一方面,使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围,避免Cache的盲目使用。否则,脏数据的出现会给系统的正常运行带来很大的隐患。
6.总结
Hibernate与MyBatis都可以是通过SessionFactoryBuider由XML配置文件生成SessionFactory,然后由SessionFactory 生成Session,最后由Session来开启执行事务和SQL语句。
而MyBatis的优势是MyBatis可以进行更为细致的SQL优化,可以减少查询字段,并且容易掌握。
Hibernate的优势是DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。