[Java]Mybatis学习笔记(动力节点老杜)(六)

简介: [Java]Mybatis学习笔记(动力节点老杜)(六)

在 IDEA 中配置 MyBatis 配置文件模版

新建 MyBatis 核心配置文件模版

<?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>
    <properties resource=""/>
    <typeAliases>
        <package name=""/>
    </typeAliases>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name=""/>
    </mappers>
</configuration>

测试使用新建的MyBatis核心配置文件模版

在 IDEA 中配置 SqlMapper 配置文件模版

新建 SqlMapper 配置文件模版

<?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="">
</mapper>

测试使用新建的SqlMapper配置文件模版

插入数据时获取自动生成的主键

业务场景说明

  • 存在业务场景,当我们向一张表中插入了一条数据,由于主键一般是自增的,在插入数据时,一般不指定主键,但是此时还有另外一张表,与新插入数据的表有关联关系,该表的外键需要使用新插入的数据的主键值,建立数据之间的关联关系,此时我们就需要获取自动生成的主键值
  • 业务背景:
  • 一个用户有多个角色:插入一个用户数据的同时需要给该用户分配角色:需要将生成的用户的id插入到角色表的user_id字段上
  • 实现方式有两种:
  • 第一种方式:可以先插入用户数据,再写一条查询语句获取新插入用户数据的id,然后再在角色表插入user_id字段。【比较麻烦】
  • 第二种方式:mybatis提供了一种方式更加便捷。

接口声明

/**
 * 插入汽车信息,同时使用生成的主键值
 *
 * @param car 汽车信息
 * @return 影响数据库的条数
 */
int insertCarUseGeneratedKey(Car car);

SQL

<!-- 
  useGeneratedKeys="true" 开启使用自动生成的主键
  keyProperty="id" 指定要将自动生成的主键值保存到用于
           传递数据给SQL语句的对象的哪个属性上
-->
<insert id="insertCarUseGeneratedKey" useGeneratedKeys="true" keyProperty="id">
  insert into t_car 
  values (null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType});
</insert>

测试

@Test
public void testInsertCarUseGeneratedKey() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car = new Car(null, "9199", "凯美瑞", 32.0, "2020-11-12", "电车");
    int count = mapper.insertCarUseGeneratedKey(car);
    System.out.println("影响数据库的条数:" + count);
    System.out.println(car);
    SqlSessionUtil.close();
}

Mybatis 参数处理

  • 数据库表:t_student
USE dbtest;
DROP TABLE IF EXISTS t_student;
CREATE TABLE t_student (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255),
    age INT,
    height DOUBLE,
    birth DATE,
    sex CHAR(1)
);
INSERT INTO t_student(name, age, height, birth, sex) VALUES ('张三', 20, 1.77, '2001-01-02', '男');
INSERT INTO t_student(name, age, height, birth, sex) VALUES ('李四', 20, 1.67, '2001-11-22', '女');
  • 学生类:Student
package cw.study.mybatis.pojo;
import java.util.Date;
/**
 * ClassName: Student
 * Package: cw.study.mybatis.pojo
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-28 11:49
 * @Version 1.0
 */
public class Student {
    private Long id;
    private String name;
    private Integer age;
    private Double height;
    private Character sex;
    private Date birth;
    // constructor
    // setter and getter
    // toString
}

单个简单类型参数

  • 简单类型包括:
  • byte short int long float double char
  • Byte Short Integer Long Float Double Character
  • String
  • java.util.Date
  • java.sql.Date
  • select 标签中有个 parameterType属性:告诉mybatis框架,我这个方法的参数类型是什么类型。
  • 明确指明类型,MyBatis 可以不用进行类型推断,可以提高效率
<!--
  select 标签中有个 parameterType属性
    告诉mybatis框架,我这个方法的参数类型是什么类型。
  mybatis框架自身带有类型自动推断机制,所以大部分情况下parameterType属性都是可以省略不写的。
-->
<select id="selectById" resultType="Student" parameterType="java.lang.Long">
  select * from t_student where id = #{id}
</select>
  • mybatis框架自身带有类型自动推断机制,所以大部分情况下parameterType属性都是可以省略不写的。
SQL语句编译后:select * from t_student where id = ?
JDBC代码是一定要给 ? 传值的,会调用 ps.setXxx(第几个问号, 传什么值);
  ps.setLong(1, 1L);
  ps.setString(1, "zhangsan");
  ps.setDate(1, new Date());
  ps.setInt(1, 100);
  ...
<!-- mybatis框架实际上内置了很多别名,可以参考开发手册 -->
<select id="selectByName" resultType="Student" parameterType="string">
  select * from t_student where name = #{name}
</select>
  • 在#{}中,可以明确告诉mybatis框架,该参数会用什么类型的数据进行填充,对应数据库中的数据类是什么
  • 明确指明类型,MyBatis 可以不用进行类型推断,可以提高效率
<select id="selectByName" resultType="Student" parameterType="string">
  select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
</select>
  • 根据id查询、name查询、birth查询、sex查询
package cw.study.mybatis.mapper;
import cw.study.mybatis.pojo.Student;
import java.util.Date;
import java.util.List;
/**
 * ClassName: StudentMapper
 * Package: cw.study.mybatis.mapper
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-28 11:55
 * @Version 1.0
 */
public interface StudentMapper {
    // 根据id查询
    List<Student> selectById(Long id);
    // name查询
    List<Student> selectByName(String name);
    // birth查询
    List<Student> selectByBirth(Date birth);
    // sex查询
    List<Student> selectBySex(Character sex);
}
<?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="cw.study.mybatis.mapper.StudentMapper">
  <!--
    select 标签中有个 parameterType属性
      告诉mybatis框架,我这个方法的参数类型是什么类型。
    mybatis框架自身带有类型自动推断机制,所以大部分情况下parameterType属性都是可以省略不写的。
  -->
  <select id="selectById" resultType="Student" parameterType="java.lang.Long">
    select * from t_student where id = #{id}
  </select>
  <!-- mybatis框架实际上内置了很多别名,可以参考开发手册 -->
  <select id="selectByName" resultType="Student" parameterType="string">
    select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
  </select>
  <select id="selectByBirth" resultType="Student" parameterType="date">
    select * from t_student where birth = #{birth}
  </select>
  <select id="selectBySex" resultType="Student">
    select * from t_student where sex = #{sex}
  </select>
</mapper>
/**
 * ClassName: StudentMapperTest
 * Package: cw.study.mybatis.test
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-28 11:59
 * @Version 1.0
 */
public class StudentMapperTest {
    @Test
    public void testSelectById() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> students = mapper.selectById(1L);
        students.forEach(System.out::println);
        SqlSessionUtil.close();
    }
    @Test
    public void testSelectByName() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> students = mapper.selectByName("李四");
        students.forEach(System.out::println);
        SqlSessionUtil.close();
    }
    @Test
    public void testSelectByBirth() throws ParseException {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        List<Student> students = mapper.selectByBirth(simpleDateFormat.parse("2001-11-22"));
        students.forEach(System.out::println);
        SqlSessionUtil.close();
    }
    @Test
    public void testSelectBySex() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> students = mapper.selectBySex('男');
        students.forEach(System.out::println);
        SqlSessionUtil.close();
    }
}

Map 集合

  • 使用Map集合进行参数的传递,将每个条件以key和value的形式存放到集合中,在使用集合中的数据的时候需要通过#{map集合的key}来取值。
/**
 * 通过集合传递要插入数据库中的学生信息
 * 
 * @param map 学生信息
 * @return 影响数据库的条数
 */
int insertStudentByMap(Map<String, Object> map);
<!--
  parameterType="map" 可以省略, 可以自动推出传入的参数为map集合
  在使用集合中的数据的时候需要通过#{map集合的key}来取值。
-->
<insert id="insertStudentByMap" parameterType="map">
  insert into t_student(id, name, age, sex, height, birth)
  values (null, #{name}, #{age}, #{sex}, #{height}, #{birth});
</insert>
@Test
public void testInsertStudentByMap() {
    // 封装数据
    HashMap<String, Object> map = new HashMap<>();
    // name, age, sex, height, birth
    map.put("name", "张三");
    map.put("age", 20);
    map.put("sex", '男');
    map.put("height", 1.80);
    map.put("birth", new Date());
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    int count = mapper.insertStudentByMap(map);
    System.out.println(count);
    sqlSession.commit();
    SqlSessionUtil.close();
}

实体类 POJO

  • 使用实体类pojo进行参数的传递,将数据封装到实体类中,在使用实体类中的数据的时候需要通过#{get方法去掉get首字母小写}来取值。
/**
 * 通过实体类传递要插入数据库的学生信息
 * 
 * @param student 学生信息
 * @return 影响数据库记录的条数
 */
int insertStudentByPojo(Student student);
<!--
  parameterType="student" 可以省略,可以自动推出类型为Student类
    可以使用 student 是因为在MyBatis核心配置文件中配置了别名
  在使用集合中的数据的时候需要通过#{get方法去掉get首字母小写}来取值。
-->
<insert id="insertStudentByPojo" parameterType="student">
  insert into t_student(id, `name`, age, sex, height, birth)
  values (null, #{name}, #{age}, #{sex}, #{height}, #{birth});
</insert>
@Test
public void testInsertStudentByPojo() {
  // 封装数据
    Student student = new Student(null, "李四", 23, 1.78, '男', new Date());
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    int count = mapper.insertStudentByPojo(student);
    System.out.println(count);
    sqlSession.commit();
    SqlSessionUtil.close();
}

多参数

  • 如果是操作数据库的方法的形参列表是多个参数的话,mybatis框架会自动创建一个Map集合。并且Map集合是以这种方式存储参数的:
map.put("arg0", name);
map.put("arg1", sex);
map.put("param1", name);
map.put("param2", sex);
  • 注意:低版本的mybatis中,使用的是:#{0}和#{1},以及#{2}…,使用mybatis3.4.2之前的版本时:要用#{0}和#{1}这种形式。
  • 高版本的mybatis中,使用的是:
// arg 从 0 开始
#{arg0}
#{arg1}
#{arg2}
// param 从 1 开始
#{param1}
#{param2}
#{param3}
  • 对于操作数据库的方法的形参列表是多个参数的情况,在SQL语句中取数据不能直接使用形参的参数名,会报错
/**
 * 根据学生的姓名和性别进行学生信息的查询
 * 
 * @param name 学生的姓名
 * @param sex 学生的性别
 * @return 学生信息组成的集合
 */
List<Student> selectByNameAndSex(String name, Character sex);
<!-- parameterType="" 不用写了 -->
<select id="selectByNameAndSex" resultType="student">
  select * from t_student
  where `name` = #{arg0} and sex = #{param2};
</select>
@Test
public void testSelectByNameAndSex() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> students = mapper.selectByNameAndSex("张三", '男');
    students.forEach(System.out::println);
    SqlSessionUtil.close();
}

@Param(命名参数)

  • 对于操作数据库的方法中形参列表的参数为多个的情况,如果使用arg0 arg1 param1 param2这种方式的话,代码的可读性太差。
  • 在MyBatis中,为我们提供了@Param注解,使我们可以不用arg0 arg1 param1 param2,@Param注解使我们可以自定义存放参数的map集合的key,可以增强代码的可读性。

@Param 的使用

/**
 * 根据学生的姓名和性别进行学生信息的查询
 *
 * @param name 学生的姓名
 * @param sex 学生的性别
 * @return 学生信息组成的集合
 */
List<Student> selectByNameAndSexAnnotation(
        // 使用@Param注解指定参数在map集合中的key
      // @Param注解中的value属性用于指定参数在map集合中的key
        @Param("name") String name, 
        @Param("sex") Character sex
);
  • 使用Param注解后,mybatis框架底层的实现原理:
map.put("name", name);
map.put("sex", sex);
<select id="selectByNameAndSexAnnotation" resultType="student">
  select * from t_student
  where `name` = #{name} and sex = #{sex};
</select>
@Test
public void testSelectByNameAndSexAnnotation() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> students = mapper.selectByNameAndSexAnnotation("张三", '男');
    students.forEach(System.out::println);
    SqlSessionUtil.close();
}

注意点

  • 使用了@Param注解之后,arg0和arg1失效了
<select id="selectByNameAndSexAnnotation" resultType="student">
  select * from t_student
  where `name` = #{arg0} and sex = #{arg1};
</select>

  • 使用了@Param注解之后,param1和param2还可以用
<select id="selectByNameAndSexAnnotation" resultType="student">
  select * from t_student
  where `name` = #{param1} and sex = #{param2};
</select>

@Param源码分析

源码跟踪
// mapper 实际上指向了代理对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// selectByNameAndSexAnnotation 是代理对象 mapper 的代理方法
// 执行到这时,会调用代理对象mapper的代理方法
List<Student> students = mapper.selectByNameAndSexAnnotation("张三", '男');
  • 调用代理对象mapper的代理方法,就是执行 MapperProxy 的 invoke 方法
// Object proxy:代理对象
// Method method:目标方法 
// Object[] args:目标方法的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
    } catch (Throwable var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }
}
  • 接下来执行 this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession)
// MapperProxy 内部类 PlainMethodInvoker 的方法
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return this.mapperMethod.execute(sqlSession, args);
}
  • 执行 MapperMethod 的 execute 方法进行 SQL 语句类和代理目标方法返回值类型的匹配
// MapperMethod 的方法
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    Object param;
    // this.command.getType() 获取 SQL 语句的类型(insert|update|select|delete)
    switch (this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            // this.method.returnsVoid() 我们定义的Mapper接口中的方法无返回值
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
                // 我们定义的Mapper接口中的方法返回多条记录
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
    }
    if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
        throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
    } else {
        return result;
    }
}
  • SQL 语句的类型以及返回结果的类型匹配完成后,进行Mapper接口代理方法的执行
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    // 将Mapper接口参数组成的数组转换为SQL语句中占位符填充的参数
    // 转换成 param1 = value1 param2 = value2 ... 的形式
    // 或者 arg1 = value1 arg2 = value2 ... 的形式
    // 或者 'name' = value1 'sex' = value2 ... 的形式
    Object param = this.method.convertArgsToSqlCommandParam(args);
    List result;
    if (this.method.hasRowBounds()) {
        RowBounds rowBounds = this.method.extractRowBounds(args);
        result = sqlSession.selectList(this.command.getName(), param, rowBounds);
    } else {
        result = sqlSession.selectList(this.command.getName(), param);
    }
    if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
        return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    } else {
        return result;
    }
}
public Object convertArgsToSqlCommandParam(Object[] args) {
    return this.paramNameResolver.getNamedParams(args);
}
  • 调用 ParamNameResolver 类的 getNamedParams(args) 方法进行转换
public Object getNamedParams(Object[] args) {
    // private final SortedMap<Integer, String> names;
    // 是一个Map集合,key为第几个参数,value为参数对应的名字
    int paramCount = this.names.size();
    if (args != null && paramCount != 0) {
        // 判断方法参数上是否有Param注解
        if (!this.hasParamAnnotation && paramCount == 1) {
            Object value = args[(Integer)this.names.firstKey()];
            return wrapToMapIfCollection(value, this.useActualParamName ? (String)this.names.get(0) : null);
        } else { // 有Param注解走该分支
            // 有Param注解会新建一个Map集合 param集合
            Map<String, Object> param = new MapperMethod.ParamMap();
            int i = 0;
          // 遍历存储参数名字的Map集合
            for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
                Map.Entry<Integer, String> entry = (Map.Entry)var5.next();
                // 向param集合中添加数据,key为接口方法参数的名字 entry.getValue()
                // value为接口方法参数的参数值 args[(Integer)entry.getKey()]
                param.put(entry.getValue(), args[(Integer)entry.getKey()]);
                // genericParamName 通用参数名
                String genericParamName = "param" + (i + 1); // param1 param2 ...
                // 集合是否包含通用参数名 param1 param2 ...
                if (!this.names.containsValue(genericParamName)) {
                    // 这个就是可以使用 param1 param2 获取参数值
                    // 向集合中添加 param1=value1 param2=value2
                    param.put(genericParamName, args[(Integer)entry.getKey()]);
                }
            }
            return param;
        }
    } else {
        return null;
    }
}
图示分析

MyBatis查询语句专题

返回 POJO(一条记录)

public interface CarMapper {
    /**
     * 根据id查询汽车信息
     * 
     * @param id 汽车信息的id
     * @return 汽车信息
     */
    Car selectById(Long id);
}
<!-- 
  如果SQL语句中不使用别名,
  则读取数据后字段名与对象的属性名不一致的话
  不一致的属性的值会为null
-->
<select id="selectById" resultType="car">
  select id,
         car_num      as carNum,
         brand,
         guide_price  as guidePrice,
         produce_time as produceTime,
         car_type     as carType
  from t_car
  where id = #{id};
</select>
@org.junit.Test
public void testSelectById() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car = mapper.selectById(5L);
    System.out.println(car);
    SqlSessionUtil.close();
}

  • 如果返回的结果集可能有多条记录,则不能使用一个pojo对象进行接收,否则会报TooManyResultsException,返回的结果集可能有多条数据记录,则需要使用集合进行接收
相关文章
|
1月前
|
Java 编译器 开发工具
Java基础学习笔记——idea篇
JDK由JRE(包含JVM和核心类库)和开发工具箱(如javac编译器和java运行工具)组成。Java项目结构包括Project、Module、Package和Class。IDEA快捷键包括:生成main方法(main psvm)、复制代码(Ctrl+D)、删除代码(Ctrl+Y/X)、格式化代码(Ctrl+Alt+L)、重命名(Shift+F6)等。
17 0
|
2月前
Mybatis+mysql动态分页查询数据案例——分页工具类(Page.java)
Mybatis+mysql动态分页查询数据案例——分页工具类(Page.java)
28 1
|
2月前
Mybatis+mysql动态分页查询数据案例——工具类(MybatisUtil.java)
Mybatis+mysql动态分页查询数据案例——工具类(MybatisUtil.java)
17 1
|
7天前
|
SQL Java 数据库连接
15:MyBatis对象关系与映射结构-Java Spring
15:MyBatis对象关系与映射结构-Java Spring
27 4
|
15天前
|
SQL Java 数据库连接
Java从入门到精通:3.1.2深入学习Java EE技术——Hibernate与MyBatis等ORM框架的掌握
Java从入门到精通:3.1.2深入学习Java EE技术——Hibernate与MyBatis等ORM框架的掌握
|
24天前
|
XML Java 数据库连接
java对象有集合mybatis如何映射
java对象有集合mybatis如何映射
18 4
|
2月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Utils层(MybatisUtils.java)
mybatis简单案例源码详细【注释全面】——Utils层(MybatisUtils.java)
14 0
|
2月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——测试层(UserMapperTest.java)
mybatis简单案例源码详细【注释全面】——测试层(UserMapperTest.java)
10 0
|
2月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Dao层接口(UserMapper.java)
mybatis简单案例源码详细【注释全面】——Dao层接口(UserMapper.java)
10 0
|
22小时前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
6 1