JavaWeb——MyBatis学习

170 阅读11分钟

Mybatis

Mybatis介绍

  • Mybatis是一款优秀的持久层框架,是用于简化JDBC开发

    • 持久层就是负责将数据保存到数据库的那一层代码

实际上,之前文章关于JDBC的学习(juejin.cn/post/743636…),其代码书写,我们已经能够一定程度上感受到其缺陷:

    public static void main(String[] args) throws  Exception{
            //获取驱动、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //连接数据库
            String url ="jdbc:mysql://127.0.0.1:3306/learnjdbc";
            String username = "root";
            String psw = "nylonmin747599";
            //建立连接
            Connection conn = DriverManager.getConnection(url, username, psw);
            String sql = "select * from students where name like ? and score >?";
            //为上面的sql语句,创建PreparedStatement对象
            PreparedStatement preparedStatement = conn.prepareStatement(sql);
            //分别测试sql语句中问号占位符的值
            preparedStatement.setString(1, "小%");
            preparedStatement.setInt(2, 80);
            //执行查询语句,前面实例化PreparedStatement的时候已经把sql放进去了,这里就不用
            ResultSet resultSet = preparedStatement.executeQuery();
            while(resultSet.next()){
                System.out.println(resultSet.getString("name"));
            }
            preparedStatement.close();
            conn.close();
        }

  1. 硬编码 : 所谓硬编码就是把某些关键信息直接作为字符串写入到代码中,在之后如果需要修改什么的,那就会很麻烦​。

    • MyBatis对于这种东西的解决方案就是,使用配置文件,把关键信息都放入到配置文件中。
    • 比如注册驱动,获取连接
    • SQL语句
  2. 操作繁琐 ,比如使用prepareStatement需要一个个来设置参数并且获取结果也只能一个个获取。Mybatis直接自动完成

    • 繁琐在于: 手动设置参数
    • 繁琐在于:手动封装结果集

Mybatis快速入门

MyBatis 中文网 官网

IDEA连接了mysql数据库后,表不显示解决方案

解决IDEA连接数据库后,xml中写SQL语句不提醒数据库表字段问题

这里实际上也要转换思维,并不要认为Mybatis是什么高大上的东西,它只不过是一个框架,对JDBC原始操作进行了封装。从结果上来看,Mybatis框架进行的操作与JDBC并没有任何不同。

这里借鉴黑马程序员教学的示例,以此来说Mybatis一般且原始的操作。

  1. 创建数据库表,比方说user表,添加数据

  2. 创建模块,导入坐标

  3. 编写MyBatis核心配置文件 --> 替换连接信息,解决硬编码问题

  4. 编写SQL映射文件 ——>统一管理sql语句,解决硬编码问题

  5. 编码

    1. 定义POJO类
    2. 加载核心配置文件,获取SqlSessionFactory 对象
    3. 获取SqlSessionFactory对象,执行SQL语句
    4. 释放资源

这里就一句上面的过程一步步来说明:

1、创建数据库表不必多说

2、创建模块,导入坐标。这里直接使用IDEA创建Maven项目,通过导入mybatis坐标的方式来达到导入jar包的目的。

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.13</version>
    </dependency>

3、编写Mybatis核心配置文件,放在resource下面

 <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <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://127.0.0.1:3306/mybatis?useSSL=false"/>
                    <property name="username" value="root"/>
                    <property name="password" value="nylonmin747599"/>
                </dataSource>
            </environment>
        </environments>
    ​
        <!-- 指定sql映射文件所在位置 -->
        <!-- 加载sql映射文件 -->
        <mappers>
            <mapper resource="UserMapper.xml"/>
        </mappers>
    </configuration>

1、这里就是为了解决硬编码问题。避免了原生JDBC在连接数据库时,无论是获取mysql驱动还是通过驱动与数据库建立连接。

2、指定sql映射文件所在位置以及加载sql映射文件,都是为了简化后续sql语句的书写以及复用。

4、建立SQL映射文件,这里就指的是UserMapper.xml

下面这里namespace名称空间实际上就有点类似于是一个标识符,表示这一整个Mapper文件。 下面的<select> 标签里面的 id 实际上指代的是这一条sql语句的唯一标识

<?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--
        namespace 名称空间
    -->
    <mapper namespace="user">
        <select id="selectAllUser" resultType="com.itheima.pojo.User">
            select * from tb_user
        </select>
    </mapper>

5、编码,定义pojo类

▲ 这里需要特别注意,定义的pojo类的成员变量需要与数据库表中的字段类型相同,并且成员变量名也要和数据库表中的字段名相同。


    public class Demo {
        public static void main(String[] args) throws Exception {
            // 加载Mybatis核心配置文件,获取SqlSessionFactory
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            //获取sqlSession对象,用它来执行sql
            SqlSession sqlSession = sqlSessionFactory.openSession();
            //执行sql
            //具体执行哪个mapper文件下的哪个sql语句,实际上是要 namespace+id
            List<User> userList = sqlSession.selectList("user.selectAllUser");
            System.out.println(userList);
        }
    }

Mapper代理开发

使用Mapper代理开发的要求

  1. 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录之下

  2. 设置SQL映射文件的namespace属性为Mapper接口全限定名

  3. 在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并且保持参数类型和返回值类型一致

  4. 编码:

    • 通过SqlSession的getMapper方法获取Mapper接口的代理对象
    • 调用对应方法完成sql的执行

下面就一步步地进行代码以及操作来一步步说明:

1、定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录之下

这里我们定义完了与SQL映射文件同名的Mapper接口 —— UserMapper

文件结构如下:

Snipaste_2024-11-12_22-20-07.png

这时可以看到,尚未达到Mapper接口与SQL映射文件在同一目录之下。

如果我移动SQL映射文件,到Mapper文件夹之下,如图:

Snipaste_2024-11-12_22-21-23.png

这里固然符合条件了,但是确从根本上不符合我们的变成要求,代码的接口又怎么能够和SQL映射文件这样子放呢?实际上,我们将整个项目进行编译(PS:通过IDEA安装Maven helper插件,然后右键整个项目->run maven-> compile )之后,就可以知道他们实际上,编译之后路径如下:

UserMapper.xml 编译后路径为:E:\java\code\MavenLearn\target\classes\UserMapper.xml

UserMapper接口编译后路径为:E:\java\code\MavenLearn\target\classes\com\itheima\Mapper\UserMapper.class

也就是说: 我们只需要在IDEA中,在项目文件下,新建一个路径,跟UserMapper接口同样即可,如下图所示:(这样子编译之后,就可以看到,编译后他俩就都在同一个路径下了)

那么此时同样mybatis-config.xml中的加载sql映射文件路径就需要改一下

   <!-- 指定sql映射文件所在位置 -->
    <!-- 加载sql映射文件 -->
    <mappers>
        <mapper resource="com/itheima/Mapper/UserMapper.xml"/>
    </mappers>

Snipaste_2024-11-12_22-27-00.png

2、设置SQL映射文件的namespace属性为Mapper接口全限定名

将UserMapper.xml内容编写如下,着重看namespace

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
    namespace 名称空间
-->
<mapper namespace="com.itheima.Mapper.UserMapper">
    <select id="selectAllUser" resultType="com.itheima.pojo.User">
        select * from tb_user
    </select>
</mapper>   

3、在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并且保持参数类型和返回值类型一致

实际上这里的返回值还是得稍微揣测一下那个select的sql语句的返回,像上面写的是select * ,那么显然就是返回多个数据

package com.itheima.Mapper;
​
import com.itheima.pojo.User;
​
import java.util.List;
​
public interface UserMapper {
    List<User> selectAllUser();
}
    

4、编码:

  • 通过SqlSession的getMapper方法获取Mapper接口的代理对象
  • 调用对应方法完成sql的执行
package com.itheima;
​
import com.itheima.Mapper.UserMapper;
import com.itheima.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 java.io.IOException;
import java.io.InputStream;
import java.util.List;
​
public class Demo {
    public static void main(String[] args) throws Exception {
        // 加载Mybatis核心配置文件,获取SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取sqlSession对象,用它来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //执行sql
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.selectAllUser();
        System.out.println(userList);
    }
}

注:正是由于第一个条件,即Mapper.xml文件和Mapper接口都需要放到同一个目录下面,那么我们可以预见的是,之后再要出现新的Mapper.xml文件一定也是放在这个目录下。那么在mybatis-config.xml文件中就可以使用包扫描来进行,即:

    <mappers>
<!--        <mapper resource="com/itheima/Mapper/UserMapper.xml"/>-->
        <!-- 包扫描方式 -->
        <package name="com.itheima.Mapper"/>
    </mappers>

仅供个人参考,我大胆猜测,第一步至关重要,第一步完成了之后,结果就是编译后mapper.xml文件和接口文件就在一个路径下了。这时候,在运行时在进行反射,直接通过反射把sql语句反射到接口中然后进行调用实现。

查询语句

简单条件查询

这里的设置基本上跟前面的一样,配置核心文件、创建pojo实体类、创建xml文件、创建对应的Mapper接口。

我们现在重点关于Mapper.xml

比如BrandMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
    namespace 名称空间
-->
<mapper namespace="com.itheima.Mapper.BrandMapper">
​
    <select id="selectAll" resultType="com.itheima.pojo.Brand">
        select * from tb_brand;
    </select>
    <select id="selectById" resultType="com.itheima.pojo.Brand">
        select * from tb_brand where id=#{id};
    </select>
</mapper>

上面代码中,我们主要关注根据指定的id来查找对应的品牌

    <select id="selectById" resultType="com.itheima.pojo.Brand">
        select * from tb_brand where id=#{id};
    </select>

0. 参数占位符

*   `#{}` : 执行SQL时,会将 `#{}`占位符替换为`?`,将来自动设置参数值。这里有没有感受到 ,实际上就有点像我们前面学习JDBC时候使用到的`PrepareStatement` ,同理的,能够有效方式SQL注入问题。

*   `${}`: 直接拼接SQL。 会存在sql注入的问题,一般很少使用

*   使用时机:

    *   参数传递,都是用 `#{}`
    *   如果要对表名、列名进行动态设置,只能使用`${}`进行sql拼接。
  1. parameterType

    • 用于设置参数类型,该参数可以省略。

    • 我们如果要用的话一般就是

          <select id="selectById" parameterType="int" resultType="com.itheima.pojo.Brand">
              select * from tb_brand where id=#{id};
          </select>
      
    • 但是实际上我们方法已经写在对应的Mapper接口中了,mybatis完全能够通过底层反射来获取参数类型,所以基本上很少写parameterType

  2. SQL语句中特殊字符的处理

比如:查询小于指定id的品牌

下面的代码就是个错误的示范,因为在.xml文件中, <符号实际上是其标签的起点,而不是作为字符小于号。

    <select id="selectById" resultType="com.itheima.pojo.Brand">
        select * from tb_brand where id < #{id};
    </select>

这时候有两种解决方式

  1. 直接使用转义字符。比如说小于号< 其转义字符就是 &lt;

  2. 使用CDATA区,比如:

        <select id="selectById" resultType="com.itheima.pojo.Brand">
            select * from tb_brand where id
            <![CDATA[
                <
            ]]>
            #{id};
        </select>
    

将特殊字符写在 <![CDATA[ …… ]]>之中,这样就会将CDATA区中的内容作为字符串来进行解析

多条件查询

完成需求: 通过数据库中statuscompany_namebrand_name 三个字段,查询中符合条件的品牌。

1、散装参数进行查询

首先看BrandMapper.xml 。实际上在sql语句层面,并没有什么区别,仍然是使用 #{}来进行参数值的注入。

    <select id="selectByCondition" resultType="com.itheima.pojo.Brand">
        select * from tb_brand
        where status =#{status}
        and company_name like #{companyName}
        and brand_name like #{brandName}
    </select>

最主要的区别在于BrandMapper接口中关于方法的书写:

因为是有多个参数,所以mybatis实际上并不知道我们要将哪个参数对应到sql语句的具体哪个参数。所以这时候就要用 @Param("xxx") 来进行标明。这里的 xxx 要和 上面xml文件中 #{} 里面的名称完完全全保持一致。

Brand selectByCondition(@Param("status")int status,@Param("companyName")String companyName,@Param("brandName")String brandName);

额外多说一句,因为我们sql语句用了模糊查询like ,那么我们传参进来实际上是要进行处理的,比如说传入的companyName 应该是 %华为% (即要加上占位符,至于是 %还是 _ ,视自己需求而定)。

2、实体类封装参数

这里由于BrandMapper.xml中的代码没有任何变化,我就不重复贴了。

关于实体类封装参数:实际上只需要保证SQL中 放入到 #{}里面的参数名和实体类属性名完完全全对应上即可设置成功。

此时BrandMapper接口中写法如下:

Brand selectByCondition(Brand brand);

由于Brand类中成员对象——公司名 设定成了 companyName 所以.xml文件的sql语句中也要写 #{companyName}

3、map集合

只需要保证sql中的参数名称和map集合的键的名称对应上,即可设置成功。

插入语句

首先还是常规操作。

1、BrandMapper接口中设定好方法

void addBrand(Brand brand);

2、在BrandMapper.xml文件中生成好对应的statement以及sql语句

<insert id="addBrand">
    insert into tb_brand (brand_name,company_name,ordered,description,status)
    values (#{brandName},#{companyName},#{ordered},#{description},#{status})
</insert>

3、编写测试方法然后执行

通过执行下面的代码,会存在一个很有意思的问题!!!那就是,测试方法正常跑通,也没有报错,但是实际一看数据库中的表数据,却什么变化都没有。

    String brandName="金立手机";
    String companyName = "金立";
    int ordered = 100;
    String description="这手机不错";
    int status=1;
    Brand brand=new Brand();
    brand.setBrandName(brandName);
    brand.setCompanyName(companyName);
    brand.setOrdered(ordered);
    brand.setDescription(description);
    brand.setStatus(status);
    ​
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //获取sqlSession对象,用它来执行sql
    SqlSession sqlSession = sqlSessionFactory.openSession();
    BrandMapper brandMapper =sqlSession.getMapper(BrandMapper.class);
    brandMapper.addBrand(brand);
    sqlSession.close();

实际上出现数据库表没变化(即没有插入数据)的原因在于 获取sqlSession对象这一行代码 SqlSession sqlSession = sqlSessionFactory.openSession();

实际上这一行代码是可以传入参数的,即

    //开启事务,需手动提交事务
    SqlSession sqlSession = sqlSessionFactory.openSession(false);
    //----------
    //关闭事务,是自动提交事务
    SqlSession sqlSession = sqlSessionFactory.openSession(true);

如果像我们之前一样不传参,那么就默认是传入的是false,此时就需要手动提交事务。

只需要在sqlSession.close()前面加入一行代码 sqlSession.commit();

ps:如果想在插入数据库之后,能够将自动递增的id主键注入到对象brand中,只需要将.xml中的代码这样写即可

        <insert id="addBrand" useGeneratedKeys="true" keyProperty="id">
            insert into tb_brand (brand_name,company_name,ordered,description,status)
            values (#{brandName},#{companyName},#{ordered},#{description},#{status})
        </insert>

删除语句差不多,就不具体介绍了。

Mybatis参数传递

Snipaste_2024-11-14_15-19-37.png

ps:如果真的是通过collection来传参,那么:

接口中的代码:

    public interface MyMapper {
        int insertUsers(@Param("users") Collection<User> users);
    }   

.xml文件中的代码

    <mapper namespace="com.example.mapper.MyMapper">
        <insert id="insertUsers">
            INSERT INTO users (username, age)
            VALUES
            <foreach collection="users" item="user" separator=",">
                (#{user.username}, #{user.age})
            </foreach>
        </insert>
    </mapper>
  

注解完成增删改查

直接说注解的应用:

  • 查询:@Select
  • 添加:@Insert
  • 修改:@Update
  • 删除:@Delete
@Select("select * from tb_brand where id = #{id}")
public User selectById(@param("id")int id);

写在前面: 人家mybatis官网也说了—— 使用注解来映射简单语句会使代码显得更加简洁,但是对于稍微复杂一点的语句,这时候再来使用注解就可能又不好用,又不好看了。