当前位置: 首页 > article >正文

后端:MyBatis

文章目录

  • 1. MyBatis
    • 1-1. Mybatis 工具类的封装
    • 1-2. Mybatis 通过集合或实体类传递参数-实现插入数据(增)
    • 1-3. MyBatis 实现删除数据(删)
    • 1-4. MyBatis 实现修改数据(改)
    • 1-5. MyBatis 实现查询数据(查)
  • 2. MyBatis 配置文件中的一些标签和属性
    • 2-1.environments标签
    • 2-2. dataSource标签
    • 2-3. properties标签
  • 3. MyBatis 面向接口进行CRUD & 小技巧
    • 3-1. mapper.xml 中 ${}和#{}的区别
    • 3-2. mapper.xml 实现模糊查询
    • 3-3. mybatis-config.xml起别名 mapper.xml使用别名
    • 3-4. mybatis-config.xml 配置mapper映射文件
    • 3-5. 在IDEA中创建mybatis配置文件和mapper映射文件模板
    • 3-6. mybatis 获取插入成功之后的主键id
  • 4.MyBatis 参数处理 & 查询专题
    • 4-1. mapper.xml映射文件之多参数
    • 4-2. MyBatis 查询结果之Map,List\<Map\>,Map\<Map\<String,Object\>\>
    • 4-3. MyBatis 查询之结果集映射
  • 5. 动态SQL
    • 5-1. 动态SQL-if标签、where标签、trim标签
    • 5-2. 动态SQL-set标签
    • 5-3. 动态SQL-choose、when、otherwise标签
    • 5-4. 动态SQL-foreach标签
    • 5-5. 动态SQL-sql、include标签
  • 6. MyBatis 之高级映射(一对一,一对多)
    • 6-1. 一对一映射-一个学生只能属于一个班级
    • 6-2. 一对多映射-一个班级拥有多个学生
    • 6-2. 一对多映射-分步查询
  • 7. MyBatis 逆向工程 generatorConfig.xml
  • 8. MyBatis 分页插件 PageHelper
  • 9. MyBatis 注解式开发

1. MyBatis

MyBatis也就是对于jdbc的加强,其实也就是对于后者的一些封装,提高程序开发效率
在这里插入图片描述
MyBatis 依赖和MySQL依赖如下:

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis</artifactId>
     <version>3.5.9</version>
 </dependency>

 <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>5.1.46</version>
 </dependency>

然后需要写一个mybatis的配置文件,在这个配置文件,需要做到的基本配置有对应数据库的连接信息,比如driver、url、username、password,另外,在这配置文件中,可以注册对应mapper xml映射文件路径。
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>

    <typeAliases>
        <!-- 为 java 类型设置别名 -->
        <typeAlias alias="User" type="com.lz.entity.User"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mytest1?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- 注册 mapper 文件 -->
        <mapper resource="UserDao.xml"/>
    </mappers>
</configuration>

xml Mapper映射文件

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    <insert id="insert">
        insert into user values(null,'赵六')
    </insert>
</mapper>

java 运行代码如下:

package com.lz;

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;

public class TestMybatis {

    @Test
    public void test() throws IOException {

        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(stream);
        SqlSession sqlSession = build.openSession();
        int count = sqlSession.insert("insert");
        System.out.println(count);
        sqlSession.commit();
    }
}

在这里插入图片描述
在上述Java代码处

Resources.getResourceAsStream("mybatis-config.xml");

表示从类的根路径去查找文件,也就是这个目录下的文件。
在这里插入图片描述
关于mybatis-config.xml配置文件的mapper映射标签属性resource和url。
在这里插入图片描述
这里resource属性值依旧是上述resource下的文件,而url属性值要求的是绝对路径,格式为 file:///绝对路径
仔细看上述java代码,可以发现上述要想往数据库中成功插入数据,需要在执行完sql语句后,进行事务提交操作。这个和mybatis-config.xml配置文件中的配置有关,如下:
在这里插入图片描述
这个type的值可以为JDBC或者MANAGED。如果设置的值为JDBC,表示mybatis自己管理事务,因此我们需要自己手动提交事务;如果设置的值为MANAGED,表示mybatis不负责管理事务,事务管理交给其他容器来进行管理,如Spring。因此如果要进行SSM或者Spring Boot项目等,这个值应该设置为JDBC。

在这里插入图片描述
为了使控制台有日志输出信息,可以直接在mybatis-config.xml配置文件中添加如下配置即可。

<setting name="logImpl" value="STDOUT_LOGGING"/>

在这里插入图片描述
上述那个打印日志信息是mybatis自带的,如果想要引入其他依赖来打印日志信息,直接修改上述value值即可(比如SLF4J、LOG4J等,前提是引入对应的依赖。)

1-1. Mybatis 工具类的封装

package com.lz.untils;

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;


public class SqlSessionUtils {

    private static SqlSessionFactory build;


    static {
        try {
            build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession(){

        return build.openSession();
    }
}

1-2. Mybatis 通过集合或实体类传递参数-实现插入数据(增)

上述执行sql语句的参数是固定,其实可以通过#{}作为占位符来进行传递参数,如下:

    <insert id="insert2">
        insert into user values(null,#{name})
    </insert>

#{}占位符中值为map集合的key值。

@Test
public void test2() {

    SqlSession sqlSession = SqlSessionUtils.getSqlSession();

    HashMap<String, Object> map = new HashMap<>();
    map.put("name","哆啦a梦");

    int count = sqlSession.insert("insert2", map);
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();

}

运行结果如下:
在这里插入图片描述
当然也可以使用具体一个类来进行传递参数,如下:

@Test
    public void test3() {

        SqlSession sqlSession = SqlSessionUtils.getSqlSession();

        User user = new User();
        user.setuName("皮卡丘");

        int count = sqlSession.insert("insert3", user);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();

    }
package com.lz.entity;

public class User {
    private Integer uId;
    private String uName;

    public void setuId(Integer uId) {
        this.uId = uId;
    }

    public void setuName(String uName) {
        this.uName = uName;
    }

    @Override
    public String toString() {
        return "User{" +
                "uId=" + uId +
                ", uName='" + uName + '\'' +
                '}';
    }
}

    <insert id="insert3">
        insert into user values(null,#{uName})
    </insert>

运行结果和上面一样,都能插入成功。

1-3. MyBatis 实现删除数据(删)

 @Test
    public void test4() {

        SqlSession sqlSession = SqlSessionUtils.getSqlSession();

        int count = sqlSession.delete("del", 11);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();

    }
<delete id="del">
   delete from user where u_id = #{uid}
</delete>

在这里插入图片描述
这里只有一个参数,因此在mapper.xml文件中占位符#{}里的变量名可以是任意符合变量命名规范的名称。

1-4. MyBatis 实现修改数据(改)

@Test
    public void test5() {

        SqlSession sqlSession = SqlSessionUtils.getSqlSession();

        HashMap<Object, Object> map = new HashMap<>();
        map.put("uid",10);
        map.put("uname","嘻嘻哈哈");

        int count = sqlSession.update("update", map);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();

    }
    <update id="update">
        update user set u_name=#{uname} where u_id =#{uid}
    </update>

在这里插入图片描述

1-5. MyBatis 实现查询数据(查)

@Test
    public void test6() {

        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        User user = sqlSession.selectOne("select",10);
        System.out.println(user);
        sqlSession.close();
    }
<resultMap id="map1" type="com.lz.entity.User">
    <result property="uId" column="u_id"/>
    <result property="uName" column="u_name"/>
</resultMap>

<select id="select" resultMap="map1">
    select u_id,u_name from user where u_id = #{uid}
</select>

这里可能需要麻烦一些,因为查询出的结果肯定是一个对象,如果不设置对应Java实体类变量与数据库表对应字段一一映射关系,那么就会报错。当然也可以给sql语句中的字段起别名也是可以的,如下:
在这里插入图片描述

如果想查询所有数据,只需要修改一下selectOne为selectList,当然不需要写入其他sql字段参数了,毕竟是查询所有数据,mapper.xml映射文件只需要修改一下sql语句即可。
在这里插入图片描述

2. MyBatis 配置文件中的一些标签和属性

MyBatis配置文件参考文档为:Mybatis 中文文档

2-1.environments标签

在这个标签下面可以配置多个数据库环境。为了区分使用的哪个数据库环境,environments标签下的每个environment标签都会有id属性,用于区分不同数据库环境,而environments标签default属性值为某个environment标签的id属性值,表示用的就是对应的数据库环境。

<environments default="development2">
   <environment id="development">
       <transactionManager type="JDBC"/>
       <dataSource type="POOLED">
           <property name="driver" value="com.mysql.jdbc.Driver"/>
           <property name="url" value="jdbc:mysql://localhost:3306/mytest1?characterEncoding=utf-8"/>
           <property name="username" value="root"/>
           <property name="password" value="root"/>
       </dataSource>
   </environment>
	
	<environment id="development2">
       <transactionManager type="JDBC"/>
       <dataSource type="POOLED">
           <property name="driver" value="com.mysql.jdbc.Driver"/>
           <property name="url" value="jdbc:mysql://localhost:3306/mytest2?characterEncoding=utf-8"/>
           <property name="username" value="root"/>
           <property name="password" value="root"/>
       </dataSource>
   </environment>

</environments>

比如上述配置文件中所使用的环境就是id为development2的数据库环境,当然这只是默认情况下(如果在最终的Java代码中没有指定对应的数据库环境)。如果在Java代码中指定了使用哪个数据库环境,那么最终使用的数据库就是那个。

package com.lz;

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;

public class TestMybatis {

    @Test
    public void test() throws IOException {

        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(stream,"数据库环境id值");
        SqlSession sqlSession = build.openSession();
        int count = sqlSession.insert("insert");
        System.out.println(count);
        sqlSession.commit();
    }
}

2-2. dataSource标签

dataSource标签为数据源标签。dataSource标签有一个type属性,这个属性有3个值可以进行选择,分别为UNPPOOLED、POOLED、JNDI。其中UNPPOOLED表示不使用数据库连接池技术,每次请求过来之后,都会创建新的Connection对象;POOLED表示使用mybatis自己实现的数据库连接池;JNDI表示集成其他第三方的数据连接池(比如Druid、C3P0等)。
type值为POOLED情况下,每次都会从连接池获取连接对象,用完之后会把连接对象归还给连接池,如下,因为用完之后又归还给了连接池,所以第二次获取得到的连接对象和第一次相同。
在这里插入图片描述
type值为UNPPOOLED情况下,每次都会创建新的连接对象,用完之后会关闭对应连接。
在这里插入图片描述

另外type属性不同,它们可以添加的属性也有所不同,如下:
在这里插入图片描述
在这里插入图片描述

2-3. properties标签

<properties resource="database.properties"/>

可以通过这个标签属性resource,让mybatis读取resources目录下的对应properties文件的数据,常用就是读取数据库连接信息,然后在配置文件下的environment标签的属性就可以进行对应赋值了。当然也可以使用url属性进行设置,只不过需要写绝对路径,格式为file:///文件的绝对路径

<environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
        <property name="driver" value="${mysql.driver}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
    </dataSource>
</environment>

properties文件数据如下:

mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mytest1?characterEncoding=utf-8
mysql.username=root
mysql.password=root

当然也可以直接在properties标签下定义property标签,在property标签内定义name和value,如下:

<properties>
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    。。。。
</properties>

在mybatis配置文件中如果要引入上述已经定义好的值,只需要使用${name值}即可。

3. MyBatis 面向接口进行CRUD & 小技巧

通过调用SqlSession对象的方法geMapper即可获取对应的Dao对象,需要传递参数对应Dao.class对象,前提是需要写接口已经对应的Mapper映射文件,如下:
UserDao接口文件

package com.lz.dao;

import com.lz.entity.User;

import java.util.List;

public interface UserDao {
    User queryUserByid(Integer id);
    // 查询
    List<User> queryUsers();
    // 查询所有
    Integer add(User user);
    // 增
    Integer del(Integer uid);
    // 删
    Integer update(User user);
    // 改
}

UserMapper 映射文件

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lz.dao.UserDao">

    <select id="queryUserByid" parameterType="Integer" resultType="com.lz.entity.User">
        select u_id as uId,u_name as uName from user where u_id=#{uid}
    </select>

    <select id="queryUsers" resultType="com.lz.entity.User">
        select u_id as uId,u_name as uName from user
    </select>

    <insert id="add" parameterType="User">
        insert into user values(null,#{uName})
    </insert>

    <delete id="del" parameterType="Integer">
        delete from user where u_id=#{uid}
    </delete>

    <update id="update" parameterType="User">
        update user set u_name = #{uName} where u_id=#{uId}
    </update>
</mapper>

需要注意的是mapper标签的namespace属性值必须为对应接口名,select、update、delete、insert 的id属性值必须为对应接口下的方法名。
单元测试运行代码:

package com.lz;

import com.lz.dao.UserDao;
import com.lz.entity.User;
import com.lz.untils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class TestInterfaceCRUD {

    @Test
    public void test1(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();

        UserDao userDao = sqlSession.getMapper(UserDao.class);

        List<User> users = userDao.queryUsers();

        users.forEach(user -> System.out.println(user));
        sqlSession.close();
    }

    @Test
    public void test3(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();

        UserDao userDao = sqlSession.getMapper(UserDao.class);

        User user = userDao.queryUserByid(10);

        System.out.println(user);
        sqlSession.close();
    }


    @Test
    public void test2(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();

        UserDao userDao = sqlSession.getMapper(UserDao.class);

        User user = new User();
        user.setuName("喜喜");
        int count = userDao.add(user);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();

    }


    @Test
    public void test4(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();

        UserDao userDao = sqlSession.getMapper(UserDao.class);

        Integer count = userDao.del(11);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();

    }

    @Test
    public void test5(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();

        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = new User();
        user.setuId(10);
        user.setuName("哆啦A梦");
        Integer count = userDao.update(user);

        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();

    }
}

3-1. mapper.xml 中 ${}和#{}的区别

#{}:底层使用PreparedStament,特点是先进行SQL语句的编译,然后给SQL语句的占位符?进行传值;
${}:底层使用Statement,特点是先进行SQL语句的拼接,然后再对SQL语句进行编译,存在SQL注入的风险
为此,优先使用#{},可以避免SQL注入的风险。
下述是使用${}进行SQL注入的示例代码,如下:
mappper映射文件:

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lz.dao.UserDao">
    <select id="queryUsersByName" resultType="com.lz.entity.User" parameterType="String">
        select u_id as uId,u_name as uName from user where u_name ='${uName}'
    </select>
</mapper>

这是数据表中的数据:
在这里插入图片描述
然后我想查询数据库表user中有多少个u_name为赵六的用户,正常java代码如下:
在这里插入图片描述
正常情况下的确查询出的结果为3个,如果进行SQL注入,查询得到结果将会是user表中的全部数据,如下:
在这里插入图片描述
那么究竟在什么情况下使用#{},在什么情况下使用${}。如果需要在SQL语句中插入SQL关键字(比如desc、asc,表名等),使用${}
比如如果每天都会生成一个表,这个表存储的信息是当天注册某个网站的用户,表名是以时间日期形式拼成的,比如t_20250101。如果想查询一下某一天注册的用户数,此时只需要查询到对应的表即可,此时就可以使用${}。mapper映射文件的sql语句为:

select count(*) from t_${} 

如果把上述sql语句修改为#{},此时最终执行的sql会是这样,如下:

select count(*) from t_'t_20250101'

3-2. mapper.xml 实现模糊查询

在mapper.xml映射文件中可以写的sql语句为:

select u_id as uId,u_name as uName from user where u_name like '%${uName}%'

个人觉得下述这个更加安全,可以防止sql注入情况。

select u_id as uId,u_name as uName from user where u_name like concat('%',#{uName},'%')

3-3. mybatis-config.xml起别名 mapper.xml使用别名

在这里插入图片描述
上述mapper.xml映射文件中是不是觉得返回结果类型写这么长一串是不是觉得特别繁琐,其实可以其个别名,这样只需要写这个别名即可,如下,需要在mybatis配置文件进行配置才行。
在这里插入图片描述
上述这个属性alias可以省略,使用的时候可以直接写类的简名即可,如User、user等,不区分大小写。为了提高开发效率,也可以使用package起别名,此时需要写包的在项目下的路径即可,这样这个包下所有的实体类都起了别名,和上面一样,使用简名,如下:

<typeAliases>
    <!-- 为 java 类型设置别名 -->
<!--        <typeAlias alias="User" type="com.lz.entity.User"/>-->
    <package name="com.lz.entity"/>
</typeAliases>

此时在mapper.xml映射文件中就可以使用对应的别名了,如下:
在这里插入图片描述

3-4. mybatis-config.xml 配置mapper映射文件

<mappers>
	<mapper resource=""></mapper>
	<mapper url=""></mapper>
	<mapper class=""></mapper>
</mappers>

mapper标签你的属性有3个,resource、url、class。resource:mapper.xml映射文件需要放到类路径才行,也就是项目的resources目录下;url:mapper.xml映射文件需要写绝对路径才行;class:这个值写的是mapper接口的全限定接口名,必须带有报名。同时mapper接口类与mapper.xml映射文件必须在同一个目录下,如下:

在这里插入图片描述

需要注意的是:在resources目录下新建目录不能采用com.lz.dao这种形式来创建多级目录,上述效果只能创建一个目录,目录名为com.lz.dao。如果要创建多级目录,只能一级一级去进行创建。最终创建出的目录显示效果和上述直接com.lz.dao这样的效果一样,但是打开项目结构就会发现不一样的。

3-5. 在IDEA中创建mybatis配置文件和mapper映射文件模板

在这里插入图片描述
点击File->settings->Editor->File and Code Templates,选择+新增模板,然后把对应的mybatis-config.xml,mapper.xml文件对应格式代码翻入之后保存即可。
在这里插入图片描述
在这里插入图片描述

3-6. mybatis 获取插入成功之后的主键id

需要在mapper映射文件中insert标签添加两个属性,分别为useGeneratedKeys,keyProperty,如下:
在这里插入图片描述
在这里插入图片描述
此时可以发现uid值不为null,是插入到数据库表的id主键。
在这里插入图片描述

4.MyBatis 参数处理 & 查询专题

mybatis框架自带有类型推断机制,所以大部分情况下mapper.xml映射文件parameterType属性都是可以省略不写的。但是写上,效率可能会更高。

4-1. mapper.xml映射文件之多参数

前面说过,如果where条件只有一个,那么在mapper.xml映射文件sql语句的编写的名可以随便写。如果是多个参数,则需要这样写:
在这里插入图片描述
可以采用的写法为arg0,arg1…或param1,param2…,表示的是在定义方法的形参位置。
当然也可以使用注解@Param来自定义命名,如下:

public interface UserDao{
	List<User> queryUserByid2(@Param(value="uId") Integer id, @Param(value = "uName") String uName);
}
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lz.dao.UserDao">
    <select id="queryUserByid2" resultType="com.lz.entity.User">
        select u_id as uId,u_name as uName,birth from user where u_id=#{uId} or u_name like concat('%',#{uName},'%')
    </select>

</mapper>

在这里插入图片描述
在java代码这里进行断点操作,然后debug,可以发现代码执行到这:
在这里插入图片描述
在这里插入图片描述
发现这里使用的是jdk动态代理,然后会跳到这里。
在这里插入图片描述

发现它最终执行的还是SqlSession对象的selectList,如下:
在这里插入图片描述

4-2. MyBatis 查询结果之Map,List<Map>,Map<Map<String,Object>>

如果查询的结果没有对应的实体类进行接收,此时可以采用Map集合进行接收,参考代码如下:

public interface UserDao{
	Map<String,Object> queryByid3(Integer uid);
}
<mapper class="com.lz.dao.UserDao">
	<select id="queryByid3" returnType="Map">
		select * from user where u_id=#{uid}
	</select>
</mapper>

运行结果依旧可以输出的。
在这里插入图片描述
如果想要查询多个数据,采用List类型进行接收,参考如下:

public interface UserDao{
	List<Map<String,Object>> queryAll2();
}
<mapper class="com.lz.dao.UserDao">
    <select id="queryAll2" resultType="Map">
        select * from user
    </select>
</mapper>

运行结果如下:
在这里插入图片描述
但是上述并不怎么好从List中去得到满足条件的Map对象,为此,在此基础之上有了Map<String,Map<String,Object>>,把里层Map的u_id值作为外层Map的键值。如下:

public interface UserDao{
	@MapKey(value = "u_id")
	Map<Integer,Map<String,Object>> queryAll3();
}

使用注解@MapKey指定查询出结果哪个字段作为map的键值。

<mapper class="com.lz.dao.UserDao">
    <select id="queryAll3" resultType="Map">
        select * from user
    </select>
</mapper>

在这里插入图片描述

4-3. MyBatis 查询之结果集映射

在上述代码中如果查询的结果需要对应到对应实体类,采用的解决方法是对查询字段起别名(1),从而保证查询出的结果字段名与Java实体类属性名一一对应。其实,在mapper.xml映射文件中除了上述方法之外,还可以定义一个 结果集(2) 标签resultMap,在这个标签下写上对应数据库表字段名与Java实体类属性名一一对应关系,如下:

<mapper class="com.lz.dao.UserDao">
    <resultMap id="user" type="com.lz.entity.User">
        <id property="uId" column="u_id"/>
        <result property="uName" column="u_name" javaType="String" jdbcType="VARCHAR"/>
        <result property="birth" column="birth"/>
    </resultMap>
	
	<select id="queryUserByid" resultMap="user">
	    select * from user where u_id=#{uid}
	</select>
</mapper>

在resultMap 下推荐使用id标签来指定主键字段对应关系,在标签内推荐使用javaType、jdbcType属性来指定对应Java类型,数据库字段类型,这样代码运行效率更高。
当然,上述效果也可以使用命名方式来实现,也就是开启 驼峰命名(3) 来实现自动映射。只需要在MyBatis配置文件中通过setting标签开发驼峰命名即可,如下:

<settings>
	<setting name="mapUnderscoreToCamelCase" value="true"/>
</setting>

需要注意的是,应用这种方式:Java实体类变量名命名需要首字母小写,后面每个单词首字母大写;SQL数据表字段全部小写,单词之间采用下划线分割。比如如下:

sql : u_name
java: uName

在这里插入图片描述

5. 动态SQL

前面看到的sql语句where条件最多两个,如果存在多个条件,并且传递过来的参数却并不满足全部条件,也就是只有一些条件有参,有一些条件无参,这样虽然使用固定sql语句以及Java代码进行逻辑判断依旧是可以实现的,但是这样做比较繁琐,且重复代码比较多,此时就可以考虑使用动态sql,这样只需要写一条sql语句,在mapper.xml映射文件中做一些判断即可实现同样的效果。

5-1. 动态SQL-if标签、where标签、trim标签

if标签的test属性是必须的;test属性值是false或者true; 如果test属性值为true,则if标签下的sql语句就会被拼接,否则,则不会被拼接。如果这个参数传递是通过函数形参进行的,在没有使用注解的情况下,只能使用param1,param2,arg0,arg1来指明对应位置的参数;在使用注解的情况下,则是使用注解对应的value值;如果参数传递是通过实体类进行传递的,则是使用实体类的变量名;如果参数传递是通过Map集合来进行传递的,则是通过Map的键来进行的。

public interface UserDao{
	List<User> queryUsersByAll(Map<String,Object> map);
}
<mapper class="com.lz.dao.UserDao">
	<select id="queryUsersByAll" resultType="User">
        select * from user
        where
        <if test="uName != null and uName !='' ">
            u_name like concat('%',#{uName},'%') or
        </if>
        <if test="startUid != null and endUid != null">
            ( u_id &gt; #{startUid} and u_id &lt; #{endUid})
        </if>
    </select>
</mapper>

上述语句在uName、startUid、endUid值都不为空的情况下是能正常运行的,但是如果三个参数有空的情况,则是有问题的,如下:
在这里插入图片描述
此时因为在sql语句多了一个关键词where,因此出现报错情况,为此可以考虑使用where标签,如下:

<mapper class="com.lz.dao.UserDao">
	<select id="queryUsersByAll" resultType="User">
        select * from user
        <where>
	        <if test="uName != null and uName !='' ">
	            u_name like concat('%',#{uName},'%') or
	        </if>
	        <if test="startUid != null and endUid != null">
	            ( u_id &gt; #{startUid} and u_id &lt; #{endUid})
	        </if>
        </where>
    </select>
</mapper>

在这里插入图片描述

但是此时如果uName不为空,其他两个值均为空,此时语句中会报错,因为多了一个or关键词(where标签也是可以实现的,但是where标签只能去掉下面拼成的sql语句中前面的or、and,不能去掉后面的。),为此,也可以考虑使用trim标签,使用这个标签下拼成的sql语句可以在前加上sql关键词(比如where),也可以在后拼接sql关键词,并且还可以过滤前(prefixOverrides)、后(suffixOverrides)多出的sql关键词(比如and、or,且可以指定对应的sql关键词。),如下:

<mapper class="com.lz.dao.UserDao">
	<select id="queryUsersByAll" resultType="User">
        select * from user
        <trim prefix="where" prefixOverrides="or | and" suffixOverrides="or | and">
            <if test="uName != null and uName !='' ">
                u_name like concat('%',#{uName},'%') or
            </if>
            <if test="startUid != null and endUid != null">
                ( u_id &gt; #{startUid} and u_id &lt; #{endUid})
            </if>
        </trim>
    </select>
</mapper>

此时再执行上述说到的sql语句效果,运行代码可以正常执行。
在这里插入图片描述

5-2. 动态SQL-set标签

主要使用在update语句当中,用来生成set关键词,同时去掉多余的“,”,其等价如下:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
<mapper class="com.lz.dao.UserDao">
	<update id="update2" parameterType="user">
        update user
        <set>
            <if test="uName != null and uName != ''">
                u_name = #{uName},
            </if>
            <if test="birth != null">
                birth = #{birth}
            </if>
        </set>
        where u_id = #{uId}
    </update>
</mapper>

经过测试,代码是可以正常输出的。
在这里插入图片描述

5-3. 动态SQL-choose、when、otherwise标签

类似java代码中if,else if,else,也就是最终只会把choose标签下面一个sql分支拼接到最终的sql中去。

if(...){
	...
}else if(...){
	...
}else{
	...
}
<mapper class="com.lz.dao.UserDao">
	<select id="queryUsers2" resultType="User">
        select * from user
        where (u_id &gt; 7 and u_id &lt; 11)
        <choose>
            <when test="uName != null and uName != ''">
                and u_name like concat('%',#{uName},'%')
            </when>
            <otherwise>
                or u_id = 14
            </otherwise>
        </choose>
    </select>
</mapper>

uName值不为空的情况下执行这条sql语句,如下:
在这里插入图片描述
uName值为空的情况下执行这条sql语句,如下:
在这里插入图片描述

5-4. 动态SQL-foreach标签

如果我想查询满足条件id的用户,此时传递过去的参数为List类型,这时就可以考虑使用foreach标签。
collection属性值可以为list、map、arg0…,当然也可以使用注解@Param来指明对应名。

<mapper class="com.lz.dao.UserDao">
	<select id="queryUsersByIds" resultType="User">
        select * from user where
        u_id in
        <foreach collection="list" item="id" index="index" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </select>
</mapper>

在这里插入图片描述

5-5. 动态SQL-sql、include标签

主要是实现sql代码的复用,如下:

<mapper class="com.lz.dao.UserDao">
	<select id="queryUsersByIds" resultType="User">
        <include refid="selectFromUser"/>
        where u_id in
        <foreach collection="list" item="id" index="index" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </select>

    <sql id="selectFromUser">
        select * from user
    </sql>
</mapper>

6. MyBatis 之高级映射(一对一,一对多)

在上面的代码,仅仅只是对一个数据库表的操作,实际业务中基本上是一对一或者一对多的情况,也就是需要至少涉及到两个数据库表。

package com.lz.entity;

public class ClassRoom {
    private Integer cId;
    private String cName;

    public void setcId(Integer cId) {
        this.cId = cId;
    }

    public void setcName(String cName) {
        this.cName = cName;
    }

    @Override
    public String toString() {
        return "ClassRoom{" +
                "cId=" + cId +
                ", cName='" + cName + '\'' +
                '}';
    }
}

package com.lz.entity;

public class Student {
    private Integer sId;
    private String sName;
    private Integer cId;

    public void setsId(Integer sId) {
        this.sId = sId;
    }

    public void setsName(String sName) {
        this.sName = sName;
    }

    public void setcId(Integer cId) {
        this.cId = cId;
    }

    @Override
    public String toString() {
        return "Student{" +
                "sId=" + sId +
                ", sName='" + sName + '\'' +
                ", cId=" + cId +
                '}';
    }
}

上述为班级类、学生类。

6-1. 一对一映射-一个学生只能属于一个班级

一个学生只能属于一个班级,因此,可以在学生类加上属性ClassRoom类型的变量,如下:

package com.lz.dao;

import com.lz.entity.Student;

public interface StudentDao {
    Student selectBySid(Integer sid);
    // 通过学生id查询学生的信息
}

对应Mappper接口如上,对应的映射文件如下:

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lz.dao.StudentDao">
    
    
    <resultMap id="studentMap" type="Student">
        <id property="sId" column="s_id" javaType="Integer"/>
        <result property="sName" column="s_name" javaType="String"/>
        <result property="cId" column="c_id" javaType="Integer"/>
        <association property="classRoom" javaType="ClassRoom">
            <result property="cId" column="c_id" javaType="Integer"/>
            <result property="cName" column="c_name" javaType="String"/>
        </association>
    </resultMap>
    
    <select id="selectBySid" resultMap="studentMap">
        select s_id,s_name,s.c_id as 'c_id',c_name from student s,classroom c
        where s.c_id = c.c_id and s_id = #{sId}
    </select>

</mapper>

上述映射文件中需要使用映射到ClassRoom这个实体类上,为此,这里使用association 标签进行结构即可。

单元测试代码及结果如下:

package com.lz;

import com.lz.dao.StudentDao;
import com.lz.entity.Student;
import com.lz.untils.SqlSessionUtils2;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class TestInterfaceCRUD2 {
    @Test
    public void test1(){

        SqlSession sqlSession = SqlSessionUtils2.getSqlSession();
        StudentDao sd = sqlSession.getMapper(StudentDao.class);
        Student student = sd.selectBySid(1);
        System.out.println(student);
        sqlSession.close();
    }
}

在这里插入图片描述

6-2. 一对多映射-一个班级拥有多个学生

一个班级拥有多个学生,因此需要在ClassRoom类中添加类型为List<Student>的变量,对应的接口如下:

package com.lz.dao;

import com.lz.entity.ClassRoom;
import java.util.List;

public interface ClassRoomDao {
    ClassRoom selectByCId(Integer cId);
}

对应映射文件如下:

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lz.dao.ClassRoomDao">

    
    <resultMap id="classRoomMap" type="ClassRoom">
        <id property="cId" column="c_id"/>
        <result property="cName" column="c_name"/>
        <collection property="students" ofType="Student">
            <result property="sId" column="s_id"/>
            <result property="sName" column="s_name"/>
        </collection>
    </resultMap>
    
    <select id="selectByCId" resultMap="classRoomMap">
        select c.c_id as 'c_id',c_name,s_id,s_name from student s,classroom c 
        where c.c_id = s.c_id and c.c_id = #{cId}
    </select>

</mapper>

这里需要用到collection标签,且在指定类型时用ofType来指定这个集合数据下类型。单元测试代码如下:

public class Test1{
	@Test
    public void test2(){

        SqlSession sqlSession = SqlSessionUtils2.getSqlSession();
        ClassRoomDao cd = sqlSession.getMapper(ClassRoomDao.class);
        ClassRoom classRoom = cd.selectByCId(1);
        System.out.println(classRoom);
        sqlSession.close();

    }
}

运行结果如下:
在这里插入图片描述

6-2. 一对多映射-分步查询

上述直接是多表查询,其实也可以通过分步查询实现上述效果的,参考如下:

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lz.dao.ClassRoomDao">

    <resultMap id="classRoomMap2" type="ClassRoom">
        <id property="cId" column="c_id"/>
        <result property="cName" column="c_name"/>
        <collection property="students" select="com.lz.dao.StudentDao.selectByCid" column="c_id"/>
    </resultMap>

    <select id="selectByCId2" resultMap="classRoomMap2">
        select c_id,c_name from classroom
        where c_id = #{cId}
    </select>

</mapper>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lz.dao.StudentDao">

    <select id="selectByCid" resultType="Student">
        select s_id,s_name,c_id from student
        where c_id = #{cId}
    </select>

</mapper>

运行结果:
在这里插入图片描述
分步操作的好处:

  • 实现代码的复用,提高程序开发效率;
  • 支持延迟加载,也就是查询出的数据只有当用到的时候才执行对应的sql语句。
    首先在mybatis配置文件中开启延迟加载设置:
<settings>
	<setting name="lazyLoadingEnabled" value="true"/>
</settings>

在这里插入图片描述
只获取班级名称只执行了一条sql语句,如上。
在这里插入图片描述
当获取对应班级的学生时,两条sql语句才执行。

7. MyBatis 逆向工程 generatorConfig.xml

所谓的逆向工程就是根据数据库表自动生成对应的实体类、对应的Mapper接口以及对应的Mapper.xml映射文件。为此需要引入对应的逆向工程插件的依赖。

<build>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.1</version>

                <configuration>
                    <overwrite>true</overwrite>
                </configuration>
                <!--                这里只是是否重写已经存在的文件,也就是覆盖-->

                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.46</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

导入依赖之后,需要在resources目录下新建文件,文件名为:generatorConfig.xml,需要注意的是,这个名字必须是这个,文件下的配置如下:

<?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>


    <properties resource="jdbc.properties"/>


    <context id="sqlserverTables" targetRuntime="MyBatis3">
<!--        targetRuntime 有两个值为:Mybatis3和Mybatis3Simple
            Mybatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查
            Mybatis3Simple:生成的是基础版,只有基本的增删改查
-->

        <!--替换默认生成的dao-Example-->
        <plugin type="org.mybatis.generator.plugins.RenameExampleClassPlugin">
            <property name="searchString" value="Example$" />
            <property name="replaceString" value="Impl" />
        </plugin>


        <commentGenerator>
            <!-- 是否生成注释代时间戳-->
            <property name="suppressDate" value="true" />
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>


        <!-- 数据库链接URL、用户名、密码 -->
        <jdbcConnection driverClass="${spring.datasource.driverClassName}"
                        connectionURL="${spring.datasource.url}"
                        userId="${spring.datasource.username}"
                        password="${spring.datasource.password}">
        </jdbcConnection>


        <!--默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer-->
        <!--true,把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal-->


<!--        <javaTypeResolver>-->
<!--            <property name="forceBigDecimals" value="false" />-->
<!--        </javaTypeResolver>-->




        <!--生成model模型,对应的包路径,以及文件存放路径(targetProject),targetProject可以指定具体的路径,如./src/main/java,-->
        <!--也可以使用“MAVEN”来自动生成,这样生成的代码会在target/generatord-source目录下-->
        <!--<javaModelGenerator targetPackage="com.joey.mybaties.test.pojo" targetProject="MAVEN">-->
        <javaModelGenerator targetPackage="com.lz.entity" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <!-- 从数据库返回的值被清理前后的空格  -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>


        <!--对应的mapper.xml文件  -->
        <sqlMapGenerator targetPackage="com.lz.dao" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>
<!--            是否开启子包-->
        </sqlMapGenerator>


        <!-- 对应的Mapper接口类文件 -->
        <javaClientGenerator type="xmlMapper" targetPackage="com.lz.dao" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <!--            是否开启子包-->
        </javaClientGenerator>


        <!-- 列出要生成代码的所有表,这里配置的是不生成Example文件 -->
        <!--    配置表信息
                        tableName  表名
                        domainObjectName    实体类名称-->
        <table tableName="hotels" domainObjectName="Hotel"/>


    </context>
</generatorConfiguration>

之后只需要运行对应的插件即可,如下:
在这里插入图片描述

之后便可以在项目下自动生成对应的文件,分别为对应实体类、对应实体类接口和Mapper.xml映射文件。把对应的Mapper.xml映射文件路径配置到MyBatis配置文件中即可使用。
在这里插入图片描述
上述使用的是Mybatis的逆向工程的增强版,具体参考上述逆向工程的配置文件的对应注释。因为使用的是增强版,所以除了生成上述三个文件之外,还生成了如下这个文件,如下:
在这里插入图片描述
这个文件命名原本是HotelExample,但是在逆向工程的配置文件中做了如下配置,因此命名变成了HotelImpl。
在这里插入图片描述
这个类是用于生成复杂的查询条件的,比如如下两个查询语句的参数都是HotelImpl类型:
在这里插入图片描述

单元测试代码如下:

public class TestOne{
	@Test
    public void test4(){
        SqlSession sqlSession = SqlSessionUtils2.getSqlSession();
        HotelMapper hotelMapper = sqlSession.getMapper(HotelMapper.class);

        HotelImpl hotel1 = new HotelImpl();
        hotel1.createCriteria().andIdBetween(2,4);

        List<Hotel> hotels = hotelMapper.selectByExample(hotel1);
        hotels.forEach(hotel->{
            System.out.println(hotel);
        });
        sqlSession.close();
    }
}

在这里插入图片描述

8. MyBatis 分页插件 PageHelper

本质上就是在查询sql语句后面拼上limit。。。字符串而已。

select * from user limit 0,3

关键词limit后面两个参数分别表示起始下标(从0开始),查询结果长度。
在这里插入图片描述
使用分页插件需要引入PageHelper依赖,如下:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>4.1.3</version>
</dependency>

之后需要在mybatis配置文件中添加对应的拦截器的配置:

  <plugins>
      <plugin interceptor="com.github.pagehelper.PageHelper"/>
  </plugins>

单元测试代码如下:

public class TestOne{
	@Test
    public void test5(){
        SqlSession sqlSession = SqlSessionUtils2.getSqlSession();
        StudentDao studentDao = sqlSession.getMapper(StudentDao.class);

        PageHelper.startPage(1,1);

        List<Student> students = studentDao.selectByCid(1);
        students.forEach(student -> System.out.println(student));


        sqlSession.close();
    }
}

在这里插入图片描述
需要注意的是startPage第一个参数至少从1开始。

9. MyBatis 注解式开发

仅仅是对单表操作推荐使用,复杂sql语句操作还是使用上述xml方式来实现哈。

package com.lz.dao;

import com.lz.entity.Student;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface StudentDao2 {
    @Select("select * from student where s_id=#{sid}")
    Student selectBySid(Integer sid);
    // 通过学生id查询学生的信息
    @Insert("insert into student values(null,#{sName},#{cId})")
    Integer add(Student stu);

    @Update("update student set s_name=#{sName} where s_id=#{sId}")
    Integer update(Student stu);

    @Delete("delete from student where s_id=#{sId}")
    Integer del(Integer sId);
}

package com.lz;

import com.lz.dao.StudentDao;
import com.lz.dao.StudentDao2;
import com.lz.entity.Student;
import com.lz.untils.SqlSessionUtils2;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class TestIntefaceCRUD3 {

    @Test
    public void test1(){

        SqlSession sqlSession = SqlSessionUtils2.getSqlSession();
        StudentDao2 studentDao2 = sqlSession.getMapper(StudentDao2.class);
        Student student = studentDao2.selectBySid(1);
        System.out.println(student);
        sqlSession.close();

    }


    @Test
    public void test2(){

        SqlSession sqlSession = SqlSessionUtils2.getSqlSession();
        StudentDao2 studentDao2 = sqlSession.getMapper(StudentDao2.class);
        Student student = new Student();
        student.setsName("小明");
        student.setcId(1);
        Integer count = studentDao2.add(student);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();

    }


    @Test
    public void test3(){

        SqlSession sqlSession = SqlSessionUtils2.getSqlSession();
        StudentDao2 studentDao2 = sqlSession.getMapper(StudentDao2.class);
        Student student = new Student();
        student.setsName("小花");
        student.setsId(6);
        System.out.println(studentDao2.update(student));
        sqlSession.commit();
        sqlSession.close();

    }


    @Test
    public void test4(){

        SqlSession sqlSession = SqlSessionUtils2.getSqlSession();
        StudentDao2 studentDao2 = sqlSession.getMapper(StudentDao2.class);
        Integer count = studentDao2.del(6);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();

    }

}

需要注意的是,我这里在MyBatis配置文件中开启了驼峰命名的,且Java实体类变量与数据库表字段命名符合对应规范的,如果你的Java类与对应数据库表字段命名不符合对应规范,可以这样操作,和在xml映射文件中设置resultMap类似。
在这里插入图片描述


http://www.kler.cn/a/513382.html

相关文章:

  • 探索 SolidJS:一款高速的前端框架
  • 2024年美赛C题评委文章及O奖论文解读 | AI工具如何影响数学建模?从评委和O奖论文出发-O奖论文做对了什么?
  • 【回忆迷宫——处理方法+DFS】
  • systemverilog中的force,release和assign
  • 【Spring】定义的Bean缺少隐式依赖
  • Spring Boot 项目启动报错 “找不到或无法加载主类” 解决笔记
  • Hive: Hive的优缺点,使用方式,判断Hive是否启动(jps),元数据的存储,Hive和Hadoop的关系
  • Elasticsearch 中,分片(Shards)数量上限?副本的数量?
  • PPT大纲:如何用python实现gitlab和jira的集成
  • 芝士AI(paperzz):最新AI论文、AI降重、AI降重工具,解决论文写作低效和AI率
  • axios的使用总结
  • Python的泛型(Generic)与协变(Covariant)
  • 总结研究ChatGPT提示词分享
  • 【人工智能】Python常用库-Keras:高阶深度学习 API
  • LabVIEW智能胎压监测
  • ARM-V9 CCA/RME QEMU环境搭建
  • 【leetcode 26】28.找出字符串中第一个匹配项的下标 | 实现 strStr()==❗不会❗==
  • Java数据结构——优先队列
  • 2025牛客寒假训练营1【代码】 A B D E G H J M
  • 线性回归笔记1-4
  • java依赖问题
  • VBA语言的区块链
  • 人工智能技术在冷链物流行业的应用前景
  • Java Web开发高级——单元测试与集成测试
  • Yearning开源MySQL SQL审核平台
  • 稳定的通信桥梁,CCLINKIE转ModbusTCP网关实现AGV运输的光速效应