MyBatis基础入门
1.MyBatis 框架概述
解决数据持久化问题的框架
1.mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,
而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
2.mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中
sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并
返回。
3.采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我
们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。
4.四个核心组件
- 构造器SqlSessionFactoryBuilder
- 工厂接口SqlSessionFactory
- 会话接口SqlSession
- 映射器SQL Mapper
2.JDBC弱点分析
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
2、Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java
代码。
3、使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能
多也可能少,修改 sql 还要修改代码,系统不易维护。
4、对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记
录封装成 pojo 对象解析比较方便。
3.ORM思想和JDBC封装
4.Mybatis快速入门
4.1 添加sql
# create database mybatis;
use mybatis;
CREATE TABLE `user` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`birthday` datetime default NULL COMMENT '生日',
`sex` char(1) default NULL COMMENT '性别',
`address` varchar(256) default NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'),(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),(46,'老王','2018-03-07 17:37:26','男','北京'),(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');
CREATE TABLE `account` (
`ID` int(11) NOT NULL COMMENT '编号',
`UID` int(11) default NULL COMMENT '用户编号',
`MONEY` double default NULL COMMENT '金额',
PRIMARY KEY (`ID`),
KEY `FK_Reference_8` (`UID`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `account`(`ID`,`UID`,`MONEY`) values (1,46,1000),(2,45,1000),(3,46,2000);
CREATE TABLE `role` (
`ID` int(11) NOT NULL COMMENT '编号',
`ROLE_NAME` varchar(30) default NULL COMMENT '角色名称',
`ROLE_DESC` varchar(60) default NULL COMMENT '角色描述',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values (1,'院长','管理整个学院'),(2,'总裁','管理整个公司'),(3,'校长','管理整个学校');
CREATE TABLE `user_role` (
`UID` int(11) NOT NULL COMMENT '用户编号',
`RID` int(11) NOT NULL COMMENT '角色编号',
PRIMARY KEY (`UID`,`RID`),
KEY `FK_Reference_10` (`RID`),
CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`),
CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user_role`(`UID`,`RID`) values (41,1),(45,1),(41,2);
4.2创建 maven 工程和添加 Mybatis3.4.5 的坐标
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
4.3创建pojo/User.java
package cn.lj.pojo;
import java.io.Serializable;
import java.util.Date;
/**
* @author lj
* @Description: 用户
* @date 2020-11-24 15:33
* @QQ 851477108
*/
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
4.4创建dao/UserDao.java
package cn.lj.dao;
import cn.lj.pojo.User;
import java.util.List;
/**
* @author lj
* @Description: 用户持久层
* @date 2020-11-24 15:35
* @QQ 851477108
*/
public interface UserDao {
/**
* 查询所有操作
* @return
*/
List<User> findAll();
/**
* 保存
* @param user
*/
void saveUser(User user);
/**
* 根据Id删除用户
* @param userId
*/
void deleteUser(Integer userId);
/**
* 更新用户
* @param user
*/
void updateUser(User user);
/**
* 根据id查询用户信息
* @param userId
* @return User
*/
User findById(Integer userId);
/**
* 根据名称模糊查询用户信息
* @param username
* @return List<User>
*/
List<User> findByName(String username);
/**
* 查询总用户数
* @return
*/
int findTotal();
/**
* 根据queryVo中的条件查询用户
* @
*/
List<User> findUserByVo();
}
4.5在resource下创建mybaits配置文件SqlMpperConfig.xml
-properties(属性)
--property
-settings(全局配置参数)
--setting
-typeAliases(类型别名)
--typeAliase
--package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
--environment(环境子属性对象)
---transactionManager(事务管理)
---dataSource(数据源)
-mappers(映射器)
--mapper
--package
<!--config约束-->
<?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">
<!-- mybatis的主配置文件 -->
<configuration>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务的类型-->
<transactionManager type="JDBC"/>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
</configuration>
4.6映射配置文件cn/lj/dao/UserDao.xml,映射配置文件指的是每个dao独立的配置文件
要求:
创建位置:必须和持久层接口在相同的包中。
名称:必须以持久层接口名称命名文件名,扩展名是.xml
坑:创建resource底下的目录是不能一次创建子目录,要一次只创建一个,不然会出现目录是cn.lj名称
<?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属性必须是dao接口的全限定类名-->
<mapper namespace="cn.lj.dao.UserDao">
<!--映射配置文件的操作配置select,id属性必须dao接口的方法名,resultType是返回结果集类型-实体类的全限定类名-->
<select id="findAll" resultType="cn.lj.pojo.User">
select * from user
</select>
<!--parameterType是参数集类型-实体类的全限定类名-->
<select id="saveUser" parameterType="cn.lj.pojo.User">
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})
</select>
<select id="updateUser" parameterType="cn.lj.pojo.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}
</select>
<select id="deleteUser">
delete from user where id=#{id}
</select>
</mapper>
4.7创建日志配置resource/log4j.properties
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
5.开始测试
5.1创建测试文件cn.lj.test.MybatisTest.java
package cn.lj.test;
import cn.lj.dao.UserDao;
import cn.lj.pojo.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.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
/**
* @author lj
* @Description:
* @date 2020-11-24 16:20
* @QQ 851477108
*/
public class MybatisTest {
private InputStream in;
private SqlSession sqlSession;
private UserDao userDao;
@Before//用于在测试方法执行之前执行
public void init() throws IOException {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapperConfig.xml");
//2.创建SqlSessionFactory工厂对象,获得数据源配置信息
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.工厂对象根据信息创建SqlSession对象
sqlSession = factory.openSession();
//4.使用SqlSession创建Dao接口的动态代理对象
userDao = sqlSession.getMapper(UserDao.class);
}
@After //用于在测试方法执行之后执行
public void destroy()throws Exception{
//提交事务,如果不提交,会回滚操作,表数据直接跳一行
sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
@Test
public void testFindAll() {
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testSave(){
User user = new User();
user.setUsername("lj");
user.setAddress("上海");
user.setSex("男");
user.setBirthday(new Date());
userDao.saveUser(user);
System.out.println("保存操作之后:"+user);
}
@Test
public void testUpdate(){
User user = new User();
user.setId(50);
user.setUsername("lj-update2");
user.setAddress("上海");
user.setSex("男");
user.setBirthday(new Date());
userDao.updateUser(user);
System.out.println("保存操作之后:"+user);
}
@Test
public void testDelete(){
userDao.deleteUser(49);
}
}
5.2 测试结果
5.3查询全过程分析
/**
* 根据名称模糊查询用户信息
* @param username
* @return List<User>
*/
List<User> findByName(String username);
/**
* 查询总用户数
* @return
*/
int findTotal();
<select id="findById" parameterType="int" resultType="cn.lj.pojo.User">
select * from user where id=#{uid}
</select>
<select id="findByName" parameterType="string" resultType="cn.lj.pojo.User">
select * from user where username like #{name}
</select>
@Test
public void testFindOne(){
User user = userDao.findById(48);
System.out.println(user);
}
@Test
public void testFindByName(){
List<User> users = userDao.findByName("%王%");
for (User user : users) {
System.out.println(user);
}
}
第二种模糊查询写法,参数一定是value,不常用
<select id="findByName" parameterType="string" resultType="cn.lj.pojo.User">
select * from user where username like '%${value}%'
</select>
@Test
public void testFindByName(){
List<User> users = userDao.findByName("王");
for (User user : users) {
System.out.println(user);
}
}
5.4查询一行和模糊查询
/**
* 根据名称模糊查询用户信息
* @param username
* @return List<User>
*/
List<User> findByName(String username);
/**
* 查询总用户数
* @return
*/
int findTotal();
<select id="findById" parameterType="int" resultType="cn.lj.pojo.User">
select * from user where id=#{uid}
</select>
<select id="findByName" parameterType="string" resultType="cn.lj.pojo.User">
select * from user where username like #{name}
</select>
@Test
public void testFindOne(){
User user = userDao.findById(48);
System.out.println(user);
}
@Test
public void testFindByName(){
List<User> users = userDao.findByName("%王%");
for (User user : users) {
System.out.println(user);
}
}
第二种模糊查询写法,参数一定是value,不常用
<select id="findByName" parameterType="string" resultType="cn.lj.pojo.User">
select * from user where username like '%${value}%'
</select>
@Test
public void testFindByName(){
List<User> users = userDao.findByName("王");
for (User user : users) {
System.out.println(user);
}
}
5.5使用selectKey标签返回保存时的用户的ID或者其他
@Test
public void testSave(){
User user = new User();
user.setUsername("lj-selectKey");
user.setAddress("上海");
user.setSex("男");
user.setBirthday(new Date());
System.out.println("保存操作之前:"+user);
userDao.saveUser(user);
System.out.println("保存操作之后:"+user);
}
<!--parameterType是参数集类型-实体类的全限定类名-->
<insert id="saveUser" parameterType="cn.lj.pojo.User">
<!-- 配置插入操作后,获取插入数据的id -->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})
</insert>
5.6ongl在用户的映射配置文件中配置
ognl 表达式:
它是 apache 提供的一种表达式语言,全称是:Object Graphic Navigation Language 对象图导航语言
它是按照一定的语法格式来获取数据的。语法格式就是使用 #{对象.对象}的方式
#{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用
getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user.
而直接写 username。
5.6ongl的用法
见6.2
6.转递pojo包装对象
6.1pojo//QeryVo.java
public class QueryVo {
private User user;
public User getUser(){
return user;
}
public void setUser(User user){
this.user = user;
}
}
6.2持久层接口映射UserMapper.xml
<select id="findUserByVo" parameterType="cn.lj.pojo.QueryVo" resultType="cn.lj.pojo.User">
select * from user where username like #{user.username}
</select>
6.3测试包装类作为参数
@Test
public void testFindByVo(){
QueryVo queryVo = new QueryVo();
User user = new User();
user.setUsername("%王%");
queryVo.setUser(user);
List<User> users = userDao.findUserByVo(queryVo);
for (User u : users) {
System.out.println(u);
}
}
7.MyBatis设计模式
使用了类加载器Resources
创建型模式
- 建造者模式Builder
- 工厂模式factory
结构性模式
- 动态代理模式
8.MyBatis第二种开发-传统DAO
使用 Mybatis 开发 Dao,有原始 Dao 开发方式和 Mapper 接口代理开发方式。
而现在主流 的开发方式是接口代理开发方式,这种方式总体上更加简便。
8.1持久层 Dao 接口
/**
* 查询所有用户
* @return
*/
List<User> findAll();
8.2独有的DAO实现类
public class UserDaoImpl implements IUserDao {
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory){
this.factory = factory;
}
@Override
public List<User> findAll() {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用SqlSession中的方法,实现查询列表
List<User> users = session.selectList("cn.lj.dao.UserDao.findAll");
//参数就是能获取配置信息的key
//3.释放资源
session.close();
return users;
}
}
8.3测试
public class MybatisTest {
private InputStream in;
private IUserDao userDao;
@Before//用于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.使用工厂对象,创建dao对象
userDao = new UserDaoImpl(factory);
}
@After//用于在测试方法执行之后执行
public void destroy()throws Exception{
//6.释放资源
in.close();
}
/**
* 测试查询所有
*/
@Test
public void testFindAll(){
//5.执行查询所有方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
}
}
8.4两种方式的区别
传统DAO:
- 有dao实现类方法需要factory对象
- 使用工厂对象,创建dao对象userDao = new UserDaoImpl(factory)
动态代理DAO:
- userDao = sqlSession.getMapper(IUserDao.class)获取dao的代理对象
- 工厂对象根据信息创建SqlSession对象sqlSession = factory.openSession()
本质区别是对工厂对象的使用
传统DAO需要将factory对象给实现类
动态代理DAO需要factory对象创建sqlSession对象,然后获取dao的代理对象
两者最终都需要得到JDBC的Connection对象连接数据库实例,底层基于JDBC操作数据库实例
都需要使用到四个核心组件
- 构造器SqlSessionFactoryBuilder
- 工厂接口SqlSessionFactory
- 会话接口SqlSession
- 映射器SQL Mapper