Mybatis
一、概述
1.首先什么是jdbc技术:
由于不同的数据库实现对数据相同的操作的命令可能不同,所以servlet在和数据库交互时,所有的数据库公司都必须按照Sun公司提供的一套标准来封装自己家数据库操作数据的方法,以此提供服务。(比如Mysql的jar包,相当于servlet连接Mysql数据库的驱动包。)程序员只需要在servlet调用驱动包就可以连接到数据库。其中程序员调用数据库的规范就是jdbc技术。
2.jdbc操作数据库的流程为:
1.加载调用数据库的驱动包(比如Mysql的jar包)
2.创建并获取数据库连接对象connection
3.通过连接对象获取会话对象statement
4.执行SQL语句。如果是查询语句的话需要通过ResultSet对象进行接收,然后关闭ResultSet对象,关闭statement对象。如果是增删改语句的话,执行完SQL语句之后直接关闭statement对象
5.关闭Connection对象
jdbc操作数据库的过程还是有点多的,而且还有以下问题:
-
尽管每一次执行完SQL语句后都关闭对象资源,但数据库连接时频繁创建、释放资源会影响系统性能。
-
SQL语句在servlet(Java文件)中,开发中如果修改的话,还需要重新进行编译。
-
对结果集的解析也是硬编码,SQL变化会导致解析结果的代码也跟着变化,系统不易维护。
3.MyBatis框架
MyBatis是一个优秀的基于Java的持久层框架,内部对jdbc做了封装,使开发者只需要关注SQL语句,而不用关注jdbc中连接和关闭对象的代码,是开发更加简单。
MyBatis采用了半个ORM的思想,而SSH中的Hibernate框架是完全的ORM思想。
ORM:Object Relationship Model 对象关系模型,包含两个映射。
第一个映射是对象和数据库做一 一映射,也就是查询出的数据会封装到对象中。
第二个映射是不使用SQL语句。就是把SQL语句封装到了自己定义的语法中。但这样不灵活:因为无法进行延迟加载和复杂的查询操作如联表查询。
由于Mybatis只把查询的数据封装到了对象中,而且使用SQL语句。所以说是半个ORM思想。
二、MyBatis的入门程序
1.准备步骤
- 创建数据库和表
- 创建Maven项目,引入坐标依赖
<dependencies>
<!--mybatis核心包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
- 编写User实体类(用于把查询到的数据封装进实体中)
实体(entity)就是数据库中的一个个表,可参考E-R图也就是实体联系图。
实体类中的属性尽量使用包装类:
第一是因为 包装类的默认值为null,而基本类型如int的默认值为0,float的默认值为0.0,char默认值为"",boolean默认值为false。null对于程序来说比较好控制,可以做空值判断。
第二是因为 数据库中的字段可能为空,如果返回字段是null的话,int类型会报错。
- 编写UserDao的接口和方法
MyBatis通过反射按照接口定义的规则生成代理实现类,代理实现类生成对象按照接口定义的标准去实现方法。
为什么这里要实现接口而不是实现一个类:因为类实现方式太具体了,已经固定了。而接口只需要定义方法而不需要实现,方法的具体内容让实现接口的类自己实现,很灵活。
2.XML配置方式
MyBatis通过在测试类中 加载 主配置文件sqlMapConfig.xml,主配置文件中有关于各个Dao接口的配置文件(比如UserDao.xml)的映射,然后通过反射获取到接口配置文件中的mapper根标签下的namespace(命名空间)和select、insert等标签下的id、resultType等属性。根据namespace的值MyBatis知道映射到哪个接口,根据select标签中的id,MyBatis知道每个SQL语句应该映射到接口中的哪个抽象方法,并最终生成一个代理实现类。
namespace和resultType等属性中的值不能直接写接口名或者实体类名,而应该写成 包名.接口 / 包名.实体类 这些 具体的路径,这是为了让MyBatis可以通过 反射 来获得类并生成对象。
有类的话 可以通过 类.class 获得类,有对象的话 可以通过 对象.getClass() 获得类。但是这里既没有类又没有对象,只能通过 Class.forName(“类的路径”)这个静态方法来反射获得类。所以这里得写全路径而不是只写接口名和类名。但是基本类型和常用的String不需要写全路径,因为做了优化。
- 在resource目录下,创建mapper(映射)文件夹。编写UserDao.xml的配置文件,用来 当MyBatis通过反射按照接口生成代理实现类然后生成对象后,将SQL语句映射到Java对象中。
<?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.qcby.dao.UserDao">
<select id="findAll" resultType="com.qcby.entity.User">
select * from user
</select>
</mapper>
- 编写主配置文件,在resource目录下创建sqlMapConfig.xml配置文件
对不同操作的事务管理:默认采用JDBC原则—操作数据时增删改操作默认不执行,因为会改动数据。就算增删改完数据也会把数据回滚回来。所以想要增删改数据的话就需要手动提交数据。查询的话是会自动提交的,因为查询没有改动数据。
连接池:原来用jdbc直接操作数据库的时候,都是创建一个connection对象,不用数据库的时候再close掉。这样的话每次使用数据库就会频繁创建、释放资源,影响性能。
连接池的作用就是为了提高性能。
连接池的基本原理是先初始化一定的数据库连接对象,并且把这些连接保存在池中。当需要建立数据库连接时,只需要从池中取出一个对象访问,使用完毕之后再放回去。这样省略了创建连接和销毁连接的过程,性能上得到提高。本质上放在内存当中,如果初始连接的数量太大的话会消耗内存资源。
<?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="mysql">
<environment id="mysql">
<!--配置事务的类型,使用本地事务策略-->
<transactionManager type="JDBC"></transactionManager>
<!--是否使用连接池 POOLED表示使用连接池,UNPOOLED表示不使用连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3308/mybatis_day01"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 加载映射配置文件 -->
<mappers>
<mapper resource="mapper/UserDao.xml"></mapper>
</mappers>
</configuration>
- 编写测试类(两种方法)
一种是SQL Session对象 直接操作数据库,另一种是SQL Session对象 生成接口的代理对象,由代理对象操作数据库
package com.qcby.test;
import com.qcby.dao.UserDao;
import com.qcby.entity.User;
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 TestUser {
@Test
public void run() throws IOException {
//加载主配置文件,目的是为了构建SqlSessionFactory对象
InputStream in = Resources.getResourceAsStream("mybatis.xml");
//创建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//创建SqlSession对象
SqlSession session = factory.openSession();
//调用查询所有的方法
//SQL Session对象 直接操作数据库
List<User> users = session.selectList("com.qcby.dao.UserDao.findAll");
//输出结果
for (User user : users) {
System.out.println(user);
}
}
@Test
public void run1() throws IOException {
//加载主配置文件,目的是为了构建SqlSessionFactory对象
InputStream in = Resources.getResourceAsStream("mybatis.xml");
//创建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//创建SqlSession对象
SqlSession session = factory.openSession();
//SQL Session对象 生成接口的代理对象,由代理对象操作数据库
//通过SqlSession对象创建UserDao接口的代理对象
UserDao mapper = session.getMapper(UserDao.class);
//代理对象调用方法
List<User> users = mapper.findAll();
//输出结果
for (User user : users) {
System.out.println(user);
}
}
}
3.注解开发
利用注解开发不再需要编写 接口对应的XML配置文件,之前在XML中写的SQL代码直接写在 接口 中。
MyBatis通过在测试类中加载主配置文件sqlMapConfig.xml,主配置文件中有 关于 接口 的映射,然后通过反射获取到接口中注解着SQL语句的抽象方法,生成代理实现对象。
- 修改 主配置文件sqlMapConfig.xml 中的映射源
<!-- 加载映射配置文件 -->
<mappers>
<!-- 之前是映射xml配置文件 -->
<!--<mapper resource="mapper/UserDao.xml"></mapper> -->
<!--<mapper resource="mapper/StudentDao.xml"></mapper>
<mapper resource="mapper/TeacherDao.xml"></mapper>-->
<!-- 现在是映射接口 -->
<mapper class="com.qcby.dao.UserDao"></mapper>
<mapper class="com.qcby.dao.StudentDao"></mapper>
<mapper class="com.qcby.dao.TeacherDao"></mapper>
</mappers>
- 在接口中的方法上方加入注解
//注解开发
//插入数据,并返回新插入数据的id 新语句 select last_insert_id()
@Insert("insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})")
@SelectKey(statement = "select last_insert_id()",keyColumn = "id",keyProperty = "id",before = false,resultType = Integer.class)
int insert(User user);
//模糊查询的两种方法
/*@Select("select * from user where username like #{name}")*/
@Select("select * from user where username like '%${value}%'")
List<User> likeSelect(String name);
//注解开发中的分步查询 来实现延迟加载
/*
当 实体类的属性名 和 数据库表中的字段名 不一致时:
当两者只是大小写差别的时候,两者字段是一致的。
只有插入其他字符如'_'时,两者字段名会不一致导致无法映射,用@Results()注解。
*/
public class Teacher {
private Integer id;
private String Tname;
private List<Student> students;
@Override
public String toString() {
return ...
}
}
public class Student {
private Integer id;
private String sname;
private String sex;
private Integer age;
private Integer tid;
//private Teacher teacher;
@Override
public String toString() {
return ...
}
}
public interface TeacherDao {
//分步查询 第一步:查询所有老师的信息
@Select("select * from teacher")
@Results(value = {
@Result(property = "id",column = "id"),
@Result(property = "Tname",column = "Tname"),
@Result(property = "students",column = "id", many = @Many(select = "com.qcby.dao.StudentDao.student",fetchType = FetchType.LAZY))
})
List<Teacher> teacher2();
}
public interface StudentDao {
//分步查询 第二步:查询老师的学生
@Select("select * from student where t_id = #{id}")
Student student();
}
- 测试类同上