JavaSSM框架从入门到精通!第二天(MyBatis(一))!

一、 Mybatis 框架

1. Mybatis 框架简介

Mybatis 是 apache 的一个开源项目,名叫 iBatis ,2010 年这个项目由 apache 迁移到了 google,并命名为 Mybatis,2013 年迁移到了 GitHub,可以在 GitHub 下载源码。

2. Mybatis 的下载:https://github.com/mybatis

3. Mybatis 解压后的目录

使用的时候需要导入核心 jar 包,依赖的 jar 包以及数据库的驱动即可使用。

二、Mybatis 概述

1. Mybatis

Mybatis 是一个基于 Java 语言的持久层框架,它内部封装了 JDBC,使开发人员只需要关注 SQL 语句本身,其他的如注册驱动,创建 Connection,配置 Statement等繁杂操作,Mybatis 已经完成。Mybatis 通过 XML 配置或注解的方式将要执行的各种 Statement 配置起来,通过 Java 对象和 Statement 中的 SQL 动态参数(类似于占位符?)进行映射形成可执行的 SQL 语句,由 Mybatis 执行后将返回结果封装成 Java 对象返回。

2. Mybatis 的原理图:

3. 开发第一个 Mybatis 应用

(1) 导入 jar 包

导入 mybatis 的核心 jar 包,依赖 jar 包以及 mysql 的驱动 jar 包

在 IDEA 中新建一个 Java 项目,然后在工程上右键,新建一个 lib 目录,将上述的所有 jar 包拷贝到 lib 目录中,然后右键 lib 目录,Add As Library 添加为工程的库:

(2) 创建数据库表

(3) 创建 JavaBean

注意:Mybatis 要求,JavaBean 的属性名需要和数据库表的字段名同名(当然,也可以不同名,不同名可以使用后面讲的 resultMap 来解决)

package com.edu.beans;

public class Student {
private long id;
private String name;
private int age;
private double score;

    public Student() {
}

    public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}

    public Student(long id, String name, int age, double score) {
this.id = id;
this.name = name;
this.age = age;
this.score = score;
}

    public long getId() {
return id;
}

    public void setId(long 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;
}

    public double getScore() {
return score;
}

    public void setScore(double score) {
this.score = score;
}

    @Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}

(4) 创建 Dao 层接口

package com.edu.dao;

import com.edu.beans.Student;

public interface IStudentDao {
void insertStudent(Student student);
}

(5) 定义映射文件 mapper.xml,放在 com.edu.dao 包下面

映射文件的定义可以参考 Mybatis 的帮助文档,搜索“mapper.dtd”:

(6) 定义主配置文件 mybatis.xml

主配置文件的定义可以参考 Mybatis 的帮助文档,搜索 config.dtd

在定义主配置文件之前,我们先定义连接数据库的四要素的属性文件:jdbc.properties,要放在类路径中,即 src 目录:

然后定义 Mybatis 的主配置文件,也是放在类路径下,即 src 目录下:

<?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-3-config.dtd 是 Mybatis 主配置文件约束文件,在 mybatis 核心jar包 mybatis-3.3.0.jar的这里:
org.apache.ibatis.builder.xml.mybatis-3-config.dtd
-->
<configuration>
<!--注册连接数据库四要素的属性文件:jdbc.properties-->
<properties resource="jdbc.properties"/>
<!--
<environments> 用于定义环境,其中可以包含多个 <environment> 的定义,它的 default 属性用于指明当前使用哪一个环境
-->
<environments default="development">
<!--<environment> 就是其中一个环境的定义,id 属性就是该环境的名称-->
<environment id="development">
<!--指定事务管理器,type="JDBC" 表示使用 JDBC 自身的事务管理机制-->
<transactionManager type="JDBC"/>
<!-- 数据源的配置,type="POOLED" 表示使用 Mybatis 内置的数据库连接池-->
<dataSource type="POOLED">
<!--
从 jdbc.properties 属性文件中读取连接数据库的四要素来给 Mybatis 的数据源的属性赋值,
这里使用 EL 表达式的形式读取属性文件
-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--指定映射文件的位置:要包含映射文件的完整路径-->
<mapper resource="com/edu/dao/mapper.xml"/>
</mappers>
</configuration>

(7) 定义 Dao 层实现类

package com.edu.dao.impl;

import com.edu.beans.Student;
import com.edu.dao.IStudentDao;
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 java.io.IOException;
import java.io.InputStream;

public class StudentDaoImpl implements IStudentDao {
//Mybatis 的会话对象
private SqlSession session;

    @Override
public void insertStudent(Student student) {
try {
//读取主配置文件 mybatis.xml,得到输入流
InputStream in = Resources.getResourceAsStream("mybatis.xml");
//创建 SqlSessionFactory 工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//由工厂对象产生 SqlSession 对象
session = factory.openSession();
//通过 SqlSession 对象调用 API 来执行映射文件mapper.xml中的 SQL 语句
/**
* SqlSession 的 insert() 方法会去读取 mapper.xml 中配置的 <insert> 语句,该方法有两个参数:
* 第一个参数:映射文件 mapper.xml 中 <insert> 标签的 id 名
* 第二个参数:传递给 mapper.xml 的 SQL 语句动态参数的对象,该对象的属性值会给同名的动态参数赋值
*/
session.insert("insertStudent", student);
//提交事务
session.commit();
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭会话 session
if (session != null){
session.close();
}
}
}
}

(8) 测试类

package com.edu.test;

import com.edu.beans.Student;
import com.edu.dao.IStudentDao;
import com.edu.dao.impl.StudentDaoImpl;

public class StudentTest {
public static void main(String[] args) {
IStudentDao studentDao = new StudentDaoImpl();
studentDao.insertStudent(new Student("晓晓",21,98.5));
}
}

4. 使用工具类

(1) 工具类

由于每次执行都需要 SqlSession 对象,而 SqlSession 对象获取比较麻烦,所以我们可以写一个工具类来获取该对象,SqlSession 是由 SqlSesstionFactory 对象创建的,通过查SqlSessionFactory 的源码,发现其是一个重量级(开销大)的对象,所以可以将 SqlSessionFactory 设计成单例的,创建对象后不去关闭它,而是等到应用退出的时候自动释放,创建工具类如下:

package com.edu.utils;

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 java.io.IOException;
import java.io.InputStream;

public class MybatisUtils {
private static SqlSessionFactory factory;

    public static SqlSession getSqlSession(){
try {
if(factory == null){//懒汉式单例模式
//读取主配置文件 mybatis.xml,得到输入流
InputStream in = Resources.getResourceAsStream("mybatis.xml");
//创建 SqlSessionFactory 工厂对象
factory = new SqlSessionFactoryBuilder().build(in);
}
} catch (IOException e) {
e.printStackTrace();
}
return factory.openSession();
}
}

(2) 修改 Dao 层实现类

package com.edu.dao.impl;

import com.edu.beans.Student;
import com.edu.dao.IStudentDao;
import com.edu.utils.MybatisUtils;
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 java.io.IOException;
import java.io.InputStream;

public class StudentDaoImpl implements IStudentDao {
//Mybatis 的会话对象
private SqlSession session;

    @Override
public void insertStudent(Student student) {
try {
session = MybatisUtils.getSqlSession();
//通过 SqlSession 对象调用 API 来执行映射文件mapper.xml中的 SQL 语句
/**
* SqlSession 的 insert() 方法会去读取 mapper.xml 中配置的 <insert> 语句,该方法有两个参数:
* 第一个参数:映射文件 mapper.xml 中 <insert> 标签的 id 名
* 第二个参数:传递给 mapper.xml 的 SQL 语句动态参数的对象,该对象的属性值会给同名的动态参数赋值
*/
session.insert("insertStudent", student);
//提交事务
session.commit();
} finally {
//关闭会话 session
if (session != null){
session.close();
}
}
}
}

(3) 测试类

package com.edu.test;

import com.edu.beans.Student;
import com.edu.dao.IStudentDao;
import com.edu.dao.impl.StudentDaoImpl;

public class StudentTest {
public static void main(String[] args) {
IStudentDao studentDao = new StudentDaoImpl();
studentDao.insertStudent(new Student("小明",23,98.5));
}
}

5. 多个映射文件

(1) 在一个项目中,一个 Dao 接口对应一个映射文件,所以一个项目中有多个映射文件时很普遍的

(2) 项目示例:

1) 新加一个映射文件:将原来的复制一份

运行:

此时运行程序报错,原因时两个 mapper 的 namespace 相同,并且 <insert> 的 id 也相同,冲突了。

2) 修改任意一个映射文件

将两个映射文件的 SQL 语句,归于不同的命名空间(namesapce),命名空间的作用用于区分 SQL 映射的同名的 id 值,例如:

此时又报错了,原因时两个映射文件都有一个 SQL 映射的 id 为 “insertStudent”,Mybatis 就不知道该用哪一个 mapper 的 “insertStudent”,因为我们在 Java 代码中并没有指定:

解决办法就是在 Java 代码的 insert() 方法的第一个参数给出包含 namespace 前缀的 SQL 语句的 id 值:

再次执行:

没有问题。

6. 主配置文件详解(mybatis.xml)

(1) 注册连接数据库的四要素属性文件(jdbc.properties)

jdbc.properties 属性文件要放在类路径下,即 src 目录下,注意,如果项目包含 resources 目录,那么 resources 目录就是工程的类路径,此时 jdbc.properties 文件就要放在 resources 目录下。

(2) 主配置文件:mybatis.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">
<!--
mybatis-3-config.dtd 是 Mybatis 主配置文件约束文件,在 mybatis 核心jar包 mybatis-3.3.0.jar的这里:
org.apache.ibatis.builder.xml.mybatis-3-config.dtd
-->
<configuration>
<!--注册连接数据库四要素的属性文件:jdbc.properties-->
<properties resource="jdbc.properties"/>
<!--
<environments> 用于定义环境,其中可以包含多个 <environment> 的定义,它的 default 属性用于指明当前使用哪一个环境
-->
<environments default="development">
<!--<environment> 就是其中一个环境的定义,id 属性就是该环境的名称-->
<environment id="development">
<!--
指定事务管理器,type="JDBC" 表示使用 JDBC 自身的事务管理机制,即使用 Connection 对象的 commit() 方法
提交事务,rollback() 方法回退事务
-->
<transactionManager type="JDBC"/>
<!-- 数据源的配置,type="POOLED" 表示使用 Mybatis 内置的数据库连接池-->
<dataSource type="POOLED">
<!--
从 jdbc.properties 属性文件中读取连接数据库的四要素来给 Mybatis 的数据源的属性赋值,
这里使用 EL 表达式的形式读取属性文件
-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--指定映射文件的位置:要包含映射文件的完整路径-->
<mapper resource="com/edu/dao/mapper.xml"/>
<mapper resource="com/edu/dao/mapper2.xml"/>
</mappers>
</configuration>

(3) 指定实体类的全类名的别名

我们在映射文件中经常会使用到实体类,如果不给出别名,那么每次都要写全类名,比较麻烦,我们可以通过如下的方式给实体类的全类名取别名,例如:

1) 没有取别名的情况:

2) 取别名方式一:<package>

使用 <package> 方式,这样的好处是可以将指定包下面的所有实体类的简单类名作为别名,例如:

mybatis.xml 主配置文件:

mapper.xml 映射文件就可以使用别名了:

3) 方式二: <typeAlias>

使用 <typeAlias> 标签指定,它有两个属性,type:全类名,alias:别名,这种方式可以取任意的别名,但是需要一个一个指定,例如:

mybatis.xml:

mapper.xml:

(4) <enviroments>标签

该标签内可以包含多个 <enviroment> 标签,即可以配置多个运行环境,其 default 属性用于表示当前使用哪一个环境,例如:

(5) <transactionManager>标签:事务管理器

其 type 属性有两个取值:

JDBC:表示使用 Connection 对象的 commit() 和 rollback() 来提交或回滚事务。 MANAGED:表示使用容器来管理事务,比如 Spring 容器。

(6) <dataSource>标签:数据源,即数据库连接池

其 type 属性有三个取值:

UNPOOLED:不使用数据库连接池,每次请求,都会创建连接对象,使用完毕关闭连接。 POOLED:使用 Mybatis 自带的数据库连接池。 JNDI:数据源可以定义到应用的外部,通过 JNDI 容器获取数据库连接。

(7) 指定映射文件的位置,有多种方式:

1) 通过 <mapper resource="">指定,例如:

<mapper resource="com/edu/dao/mapper.xml"/>

<mapper resource="com/edu/dao/mapper2.xml"/>

2) 通过 <mapper url="">指定,例如:

<mapper url="file:///F:/workspace/mapper.xml"/>

<mapper url="http://localhost:8080/chapter2/mapper.xml"/>

该方式的好处是可以将映射文件放在本地或网络中的任意位置,通过 URL 地址访问。

3) 通过 <mapper class="">指定,例如:

<mapper class="com.edu.dao.IStudentDao"/>

使用该方式需要满足的条件:

1. 映射文件名要和 Dao 接口的名称相同

2. 映射文件要和 Dao 接口放在同一个包中

3. 映射文件中的<mapper>标签的 namespace 属性值要为 Dao 接口的全类名,如下:

4) <package name="">指定映射文件

当映射文件比较多的时候,可以使用该方式,其 name 属性用于指定映射文件所在的包,它会将这个包下面的所有映射文件都导入。但是使用这种方式,需要用到我们后面讲的 mapper 动态代理,必须满足如下条件:

1. Dao 层要使用 mapper 动态代理

2. 映射文件名要和 Dao 接口的名称相同

3. 映射文件要和 Dao 接口放在同一个包中

4. 映射文件中的<mapper>标签的 namespace 属性值要为 Dao 接口的全类名

7. API 讲解

(1) Resources 类

用于读取资源文件,返回 IO 流对象,例如:

InputStream in = Resources.getResourceAsStream("mybatis.xml");

(2) SqlSessionFactoryBuilder 类

调用其 build() 方法可以创建 SqlSessionFactory 对象,SqlSessionFactoryBuilder 对象创建完工厂对象后,即可被销毁,所以一般将其定义为局部变量,方法结束,对象自动销毁,例如:

factory = new SqlSessionFactoryBuilder().build(in);

(3) SqlSessionFactory 接口

是一个重量级对象,所以一个应用只需一个该对象即可,可以使用单例模式来创建,它包含获取 SqlSession 的方法有:

1. openSession(true):创建自动提交事务功能的 SqlSession。

2. openSession(false):创建手动提交事务功能的 SqlSession。

3. openSession():创建手动提交事务功能的 SqlSession。

(4) SqlSession 接口

一个 SqlSession 对象对应一次 Mybatis 会话,会话以该对象创建开始,以该对象关闭结束,SqlSession 不是线程安全的,所以每次会话结束前,需要将其 close(),再次会话,再次创建,常用方法:

package org.apache.ibatis.session;

import java.io.Closeable;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.executor.BatchResult;

public interface SqlSession extends Closeable {
<T> T selectOne(String var1);

    <T> T selectOne(String var1, Object var2);

    <E> List<E> selectList(String var1);

    <E> List<E> selectList(String var1, Object var2);

    <E> List<E> selectList(String var1, Object var2, RowBounds var3);

    <K, V> Map<K, V> selectMap(String var1, String var2);

    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3);

    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);

    void select(String var1, Object var2, ResultHandler var3);

    void select(String var1, ResultHandler var2);

    void select(String var1, Object var2, RowBounds var3, ResultHandler var4);

    int insert(String var1);

    int insert(String var1, Object var2);

    int update(String var1);

    int update(String var1, Object var2);

    int delete(String var1);

    int delete(String var1, Object var2);

    void commit();

    void commit(boolean var1);

    void rollback();

    void rollback(boolean var1);

    List<BatchResult> flushStatements();

    void close();

    void clearCache();

    Configuration getConfiguration();

    <T> T getMapper(Class<T> var1);

    Connection getConnection();
}

(5) 源码分析

1) 输入流的关闭

我们发现,代码中获取输入流 in 之后并没有去关闭,我们分析一下,查看这个代码:

1factory = new SqlSessionFactoryBuilder().build(in);

跟进 build() 方法:

public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}

继续跟进 build() 方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();

        try {
inputStream.close();//代码里面自动帮我们关闭了输入流
} catch (IOException var13) {
}

    }

    return var5;
}

可以看到在 finally 语句块中,始终都会去关闭输入流。

2) SqlSession 的创建

SqlSession 是通过 SqlSessionFactory 的 openSession() 方法创建的,而 SqlSessionFactory 接口的实现类是 DefaultSqlSessionFactory 类,我们查看该类的 openSession() 方法:

继续跟进去:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;

    DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();//从主配置文件读取环境信息
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);//通过环境信息创建事务工厂
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//通过事务工厂创建事务对象
Executor executor = this.configuration.newExecutor(tx, execType);//创建执行 SQL 语句的执行体对象
//通过上面的对象来创建 DefaultSqlSession 对象,即 SqlSession 对象
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}

    return var8;//返回 SqlSession 对象
}

继续跟到 DefaultSqlSession 内部:

在创建 SqlSession 对象的时候,进行了一些对象的初始化,其中 dirty=false 表示现在数据库的数据还没有被修改,如果修改了数据,dirty 为 true。

3) 增删改的执行

对于 SqlSession 的 insert()、delete()、update() 方法,底层都是调用了 update() 方法,在 DefaultSqlSession 源码中可以看到:

    public int insert(String statement, Object parameter) {
return this.update(statement, parameter);
}
public int delete(String statement, Object parameter) {
return this.update(statement, parameter);
}
public int update(String statement) {
return this.update(statement, (Object)null);
}

继续跟到 update() 方法中:

public int update(String statement, Object parameter) {
int var4;
try {
this.dirty = true;//由于现在要修改数据了,所以将 dirty=true
//从映射文件中获取配置好的 statement
MappedStatement ms = this.configuration.getMappedStatement(statement);
//通过执行体执行已经填充了动态参数的 SQL 语句
var4 = this.executor.update(ms, this.wrapCollection(parameter));
} catch (Exception var8) {
throw ExceptionFactory.wrapException("Error updating database.  Cause: " + var8, var8);
} finally {
ErrorContext.instance().reset();
}

    return var4;
}

4) SqlSession 的 commit() 方法,去 DefaultSqlSession 实现类中查看

public void commit() {

        this.commit(false);

}

继续跟进去:

public void commit(boolean force) {
try {
this.executor.commit(this.isCommitOrRollbackRequired(force));//通过执行体来提交事务
this.dirty = false;//事务提交后,数据修改已经完成,将 dirty = false
} catch (Exception var6) {
throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + var6, var6);
} finally {
ErrorContext.instance().reset();
}

}

继续跟进 isCommitOrRollbackRequired(false):

private boolean isCommitOrRollbackRequired(boolean force) {

        //this.autoCommit 为 false,this.dirty 为 true,force 为 false,整个结果为 true

        return !this.autoCommit && this.dirty || force;

}

回到上面的 commit() 方法:

继续跟进 executor 的 commit() 方法,executor 的实现类是 BaseExecutor:

public void commit(boolean required) throws SQLException {
if (this.closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
} else {
this.clearLocalCache();
this.flushStatements();
if (required) {//由于上面传过来的 required 为 true,所以下面的事务会提交
this.transaction.commit();
}

    }
}

5) SqlSession 的 close() 方法,去 DefaultSqlSession 中查看

public void close() {
try {
//this.isCommitOrRollbackRequired(false):上面计算过,返回 true
this.executor.close(this.isCommitOrRollbackRequired(false));
this.dirty = false;//关闭 session 时,dirty 也要置为 false
} finally {
ErrorContext.instance().reset();
}

}

继续跟进 executor 的 close() 方法,实现类是 BaseExecutor:

public void close(boolean forceRollback) {
try {
try {
this.rollback(forceRollback);//关闭 session 之前始终会去回滚事务
} finally {
if (this.transaction != null) {
this.transaction.close();
}

        }
} catch (SQLException var11) {
log.warn("Unexpected exception on closing transaction.  Cause: " + var11);
} finally {
this.transaction = null;
this.deferredLoads = null;
this.localCache = null;
this.localOutputParameterCache = null;
this.closed = true;
}

}

通过上面分析, SqlSession 在关闭之前会先去回滚事务,然后再关闭资源,所以我们不需要在代码中写 rollback() 的代码了。

如果我们更新数据的时候成功并已经提交了事务,那么关闭 session 时就会去回滚,由于事务已经提交,回滚就没有用了,但是如果更新数据时,发生了异常,即事务没有提交,那么关闭 session 时就自动将事务回滚了。

三、单表的增删改查(CURD)

1. 开发步骤

(1) 修改 Dao 接口

(2) insertStudentCatchId(Student student):插入后用新插入的 id 初始化被插入的对象

1) 修改映射文件 mapper.xml

2) 修改 Dao 层实现类 StuentDaoImpl.java

3) 测试类

使用 Junit 单元测试,需要导入 Junit 的 jar 包:hamcrest-core-1.3.jar、junit-4.12.jar,同时我们也可以使用 log4j 来打印日志信息,需要将 log4j 的配置文件(log4j.properties)放在类路径下,即 src 目录:

编写 Junit 的单元测试:

package com.edu.test;


import com.edu.beans.Student;
import com.edu.dao.IStudentDao;
import com.edu.dao.impl.StudentDaoImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

//JUnit 的测试类
public class StudentTest {
private IStudentDao studentDao;

    @Before //被 @Before 注解的方法为初始化方法,它会在每次 @Test 修饰的方法执行之前执行一次,完成初始化工作
public void setUp(){
studentDao = new StudentDaoImpl();
}

    @After //被 @After 注解的方法为销毁方法,它会在 @Test 修饰的方法执行之后执行一次,完成资源的销毁工作
public void tearDown(){

    }

    @Test //被 @Test 注解的方法为测试方法,可以有多个这样的方法
public void testInsertStudent(){
studentDao.insertStudent(new Student("张三",21,98.5));
}

    @Test
public void testInsertStudentCatchId(){
Student student = new Student("王五",21,98.5);
System.out.println("插入数据前,student=" + student);
studentDao.insertStudentCatchId(student);
System.out.println("插入数据后,student=" + student);
}
}

(3) 删除数据:deleteStudentById

1) 修改 mapper.xml 映射文件

2) 修改 StudentDaoImpl.java

3) 测试类 StudentTest

(4) 修改数据 updateStudent

1) 修改 mapper.xml

2) 修改 StudentDaoImpl.java

3) 测试类 StudentTest

(5) 查询所有对象,返回 List:selectAllStudents

1) 修改 mapper.xml:

2) 主配置文件配置别名:

3) 修改 StudentDaoImpl.java

4) 测试类 StudentTest

(6) 查询所有对象,返回 Map:selectStudentMap

1) 修改 mapper.xml

2) 修改 StudentDaoImpl.java

要实现 selectStudentMap() 方法,需要使用 session 的 selectMap() 方法完成,该方法会将查询出来的每条记录先封装成指定对象,然后再将该对象作为 Map 的 value,将该对象的指定属性对应的字段名作为 key 封装成一个 Map 对象,方法原型:

Map<K, V> selectMap(String id, String mapKey); id:映射文件中配置的 SQL 语句的 id mapKey:查询出的 Map 使用的key,这个可以为数据库表的字段名

3) 测试类 StudentTest

(7) 查询单个对象:selectStudentById

1) 修改 mapper.xml:

<select id="selectStudentById" resultType="Student">

        select * from student where id=#{xxx}

</select>

2) 修改 StudentDaoImpl.java

2) 测试类

(8) map 作为查询条件:selectStudentsByMap

1) 修改 mapper.xml

<select id="selectStudentsByMap" resultType="Student">

        select * from student where name=#{map.name} and age = #{map.age}

</select>

2) 修改 StudentDaoImpl.java

3) 测试类 StudentTest

(9) 模糊查询:selectStudentsByName

1) 修改 mapper.xml,模糊查询有两种写法:

2) 修改 StudentDaoImpl.java

3) 测试类 StudentTest

注意:Mybatis中的 "$" 和 "#" 区别很大:其中"#"表示占位符,而"$"符号为字符串拼接("#"不存在注入式攻击,"$"可能存在注入式攻击)

示例:将上面示例的 mapper.xml 中的"#{}"改为"${}":

执行测试代码,查看日志:

可以看到,“$” 符号是字符串拼接形式,是硬编码的方式,可能存在注入式攻击危险。

然后我们又将其改为 “#{}” 动态参数方式:

<select id="selectStudentsByName" resultType="Student">

<!--注意:'%' 和 #{xxx} 之间有一个空格-->

select * from student where name like '%' #{xxx} '%'

</select>

执行测试代码,查看日志:

可以看到,“#” 是占位符的方式给参数赋值,不存在注入式攻击危险。

(10) JavaBean 属性名和表字段名不同名

映射文件中的 resultType 可以将查询结果直接封装成 JavaBean 对象返回的条件是:SQL 语句中的字段名要和JavaBean的属性同名,如果不同名,就无法创建出对应的对象返回,这里有两种解决方案:

1) 第一种:给表字段取别名,让别名和 JavaBean 属性同名

修改 JavaBean:

package com.edu.beans;

public class Student {
private long id;
private String name;
private int age;
private double tscore;

    public Student() {
}

    public Student(String name, int age, double tscore) {
this.name = name;
this.age = age;
this.tscore = tscore;
}

    public Student(long id, String name, int age, double tscore) {
this.id = id;
this.name = name;
this.age = age;
this.tscore = tscore;
}

    public long getId() {
return id;
}

    public void setId(long 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;
}

    public double getTscore() {
return tscore;
}

    public void setTscore(double tscore) {
this.tscore = tscore;
}

    @Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", tscore=" + tscore +
'}';
}
}

修改 mapper.xml,给表字段取别名,让别名和 JavaBean 的属性同名:

<select id="selectStudentById" resultType="Student">

        select id,name,age,score tscore from student where id=#{xxx}

</select>

测试类:StudentTest:

2) 第二种:使用结果映射 resultMap

可以使用结果映射 resultMap 来建立映射,完成表字段到属性的映射,达到将查询结果封装为对象的目的,resultMap 是对 resultType 的增强。

修改 mapper.xml:

测试类 StudentTest:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93697.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93697.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux下Mysql命令,创建mysql,删除mysql

在 Linux 系统下&#xff0c;您可以通过命令行来创建和删除 MySQL 数据库。以下是详细的操作步骤&#xff0c;包括创建和删除数据库、用户&#xff0c;以及常见的相关管理命令。1. 登录 MySQL在执行任何 MySQL 操作之前&#xff0c;需要先登录 MySQL。1.1 使用 root 用户登录 M…

假设检验的原理

假设检验是统计学中用于判断样本数据是否支持某个特定假设的方法。其核心思想是通过样本数据对总体参数或分布提出假设&#xff0c;并利用统计量来判断这些假设的合理性。假设检验的基本步骤如下&#xff1a;1. 假设&#xff08;Hypothesis&#xff09;在统计学中&#xff0c;假…

信号、内存共享等实现

信号&#xff08;signal&#xff09;#include <signal.h> #include <stdio.h> #include <unistd.h>void handler(int sig) {printf("收到信号: %d\n", sig); }int main() {signal(SIGUSR1, handler); // 注册用户自定义信号printf("进程 PI…

《从日常到前沿:AI 在教育、医疗、制造业的真实落地案例》文章提纲

引言&#xff1a;AI 落地的多元图景​简述 AI 从实验室走向实际应用的发展趋势​说明选择教育、医疗、制造业的原因 —— 覆盖民生与基础产业&#xff0c;落地场景具有代表性​AI 在教育领域的落地案例​个性化学习&#xff1a;如某在线教育平台利用 AI 分析学生学习数据&#…

决策树(1)

一、树模型与决策树基础决策树概念&#xff1a;从根节点开始一步步走到叶子节点得出决策&#xff0c;所有数据最终都会落到叶子节点&#xff0c;既可用于分类&#xff0c;也可用于回归。树的组成根节点&#xff1a;第一个选择点。非叶子节点与分支&#xff1a;中间决策过程。叶…

电视系统:开启视听新时代

在当今数字化浪潮席卷的时代&#xff0c;电视领域正经历着一场深刻的变革&#xff0c;而电视系统无疑是这场变革中的耀眼明星。简单来讲&#xff0c;电视系统就是互联网协议电视&#xff0c;它宛如一座桥梁&#xff0c;巧妙地利用宽带有线电视网&#xff0c;将多媒体、互联网、…

字节开源了一款具备长期记忆能力的多模态智能体:M3-Agent

猫头虎AI分享&#xff5c;字节开源了一款具备长期记忆能力的多模态智能体&#xff1a;M3-Agent 近年来&#xff0c;多模态大模型的发展迅猛&#xff0c;但如何赋予智能体类似人类的长期记忆能力&#xff0c;一直是研究中的核心挑战。字节跳动开源的 M3-Agent&#xff0c;正是面…

第十六届蓝桥杯青少组C++省赛[2025.8.10]第二部分编程题(6、魔术扑克牌排列)

参考程序&#xff1a;#include<bits/stdc.h> using namespace std; long long dp[105]; long long c(int n) {dp[0] 1;for(int i1; i< n; i){for(int j0; j<i; j){dp[i] dp[j] * dp[i -1-j];}}return dp[n]; } int main() {int n;cin >> n;cout <<c(n…

【实时Linux实战系列】实时平台下的图像识别技术

在当今数字化时代&#xff0c;图像识别技术已经广泛应用于各个领域&#xff0c;如自动驾驶、安防监控、智能医疗等。它通过计算机对图像进行分析和处理&#xff0c;从而实现对物体、场景或人的识别。实时Linux作为一种高效的实时操作系统&#xff0c;为图像识别技术提供了强大的…

IPD流程执行检查表

IPD流程执行检查表 稽查

Jmeter的安装与使用教程

基于jdk1.8版本的Jmeter的下载与安装和使用教程。 一.安装jmeter 官网下载就行下载压缩包解压就行 Jmeter下载官网&#xff1a;http://jmeter.apache.org/download_jmeter.cgi找到安装包的下载位置&#xff0c;解压进入文件夹的bin文件夹下jmeter.bat。二.配置环境变量 1、“此…

docker 数据卷、自定义镜像操作演示分享(第二期)

数据卷1.1、背景前面有个docker go web demo应用示例&#xff0c;每次为了部署go_web_demo工程&#xff0c; 需要将使用到的cp的命令将宿主主机内的go_web_demo目录下的代码文件&#xff08;一般是编译后的二进制执行文件&#xff09;复制到容器内部。 数据卷&#xff1a;将宿主…

Pandas 入门到实践:核心数据结构与基础操作全解析(Day1 学习笔记)

目录 一、Pandas 概述 1. 什么是 Pandas 二、核心数据结构 1. Series 索引 显示索引 隐式索引 创建方式 属性与方法 数据访问 索引访问 切片访问 布尔索引 2. DataFrame 创建方式 属性与数据访问 数据修改 三、索引操作 1. 索引类型 2. 核心索引方法 3. 切…

hadoop技术栈(九)Hbase替代方案

一、 核心替代方向 ‌云原生托管NoSQL服务&#xff1a;‌ ‌Google Cloud Bigtable&#xff1a;‌ 这是HBase在云端的“官方”替代品&#xff0c;兼容HBase API&#xff0c;底层存储和架构高度优化&#xff0c;提供高吞吐、低延迟、无缝扩展、完全托管的服务。‌如果追求兼容性…

深度解析 DDoS 攻击:运作机制与防御体系构建​

在网络安全领域&#xff0c;DDoS&#xff08;分布式拒绝服务&#xff09;攻击始终是企业与机构的 “心腹大患”。它通过操控大量 “傀儡主机” 发起海量请求&#xff0c;直接瘫痪目标服务器或网络链路&#xff0c;导致业务中断、用户流失甚至品牌声誉受损。今天&#xff0c;我们…

在linux系统中下载Andconda

下载前的准备工作 在开始下载 Anaconda 之前&#xff0c;做好充分的准备工作能避免后续出现不必要的麻烦&#xff0c;让整个过程更加顺畅。 确认系统架构 Linux 系统有 32 位和 64 位之分&#xff0c;不同架构对应的 Anaconda 安装包不同。你可以通过以下命令查看系统架构&a…

学习threejs,使用EffectComposer后期处理组合器(采用RenderPass、UnrealBloomPass、FilmPass渲染通道),实现交互式 3D blob

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录一、&#x1f340;前言1.1 ☘️THREE.EffectComposer 后期处…

LLM - windows下的Dify离线部署:从镜像打包到无网环境部署(亲测,包含插件部署)

一、离线部署原理 通过Docker的save/load机制实现镜像的物理介质迁移,配合Docker Compose编排文件的环境适配能力,可在完全断网的环境中快速部署复杂应用。整个过程分为在线环境准备和离线环境还原两个阶段。 二、在线环境操作 1. 环境准备 在线环境:一台可以访问互联网的…

前端学习之后端小白java的一些理论知识(框架)

一、Spring Framework 和 Spring boot的区别 核心定位 Spring Framework&#xff1a;一个全面的Java应用开发框架&#xff0c;提供核心功能如IoC容器、AOP等Spring Boot&#xff1a;Spring Framework的扩展&#xff0c;专注于简化Spring应用的初始搭建和开发过程 配置方式 Spri…

K8S的ingress

一。ingress的介绍对于NodePort和LoadBalance&#xff0c;这两种方法&#xff0c;都有缺点&#xff1a;1.NodePort方式缺点会占用很多集群的端口&#xff0c;当集群服务变多的时候&#xff0c;缺点更加显著2.LB的缺点就是每一个service都需要一个LB&#xff0c;浪费&#xff0c…