SSM之MyBatis系列(四)---- 基于代理 Dao 实现 CRUD (增删改查)操作及MyBatis配置文件标签

在实现以下内容之前,得把环境搭建好Mybatis环境搭建

一、添加操作

在 UserDao 接口中新增 saveUser() 方法

public interface UserDao {
    
    
    /**
     * 查询所有用户
     * @return
     */
    List<User> findAll();
    /**
     * 保存方法
     * @param user
     */
    void saveUser(User user);
}

在 Dao映射文件 UserDao.xml 中配置添加操作

<?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="com.Dao.UserDao">
    <!-- 配置查询所有   -->
    <select id="findAll" resultType="com.domain.User">
        select * from user;
    </select>

    <!-- 保存用户-->
    <insert id="saveUser" parameterType="com.domain.User">
        insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
    </insert>
</mapper>

细节
parameterType 属性:
  代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
sql 语句中使用#{}字符:
  它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。
  具体的数据是由#{}里面的内容决定的。
#{}中内容的写法:
  由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。必须和User类的每个属性名一一对应,不可以乱写
  属性名是实体类的getXxx()/setXxx()中Xxx部份,大多数情况下就是成员变量名,也有少数情况不是成员变量名,也就是说成员变量和属性不能等同。
  它用的是 ognl 表达式。
ognl 表达式:
  它是 apache 提供的一种表达式语言,全称是:
  Object Graphic Navigation Language 对象图导航语言
  它是按照一定的语法格式来获取数据的。
语法格式就是使用 #{对象.对象}的方式

测试添加操作

package com.test;

import com.Dao.UserDao;
import com.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.Date;
import java.util.List;

/**
 * @author zhang
 */

public class MyBatisTest {
    
    

    private InputStream in;
    private SqlSession sqlSession;
    private UserDao userDao;

    /**
     * 测试之前执行,用于初始化
     */
    @Before
    public void init() throws Exception {
    
    
        //1. 读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3. 获取SqlSession对象
        sqlSession = factory.openSession();
        //4. 使用SqlSession创建Dao的代理对象
        userDao = sqlSession.getMapper(UserDao.class);
    }
    /**
     * 测试结束执行,用于提交事务和释放资源
     */
    @After
    public void destroy() throws Exception{
    
    
        //提交事务
        sqlSession.commit();
        //释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 测试查询所有
     */
    @Test
    public void testFindAll(){
    
    
        List<User> users = userDao.findAll();
        for (User user : users){
    
    
            System.out.println(user);
        }
    }
    /**
     * 测试保存操作
     */
    @Test
    public void testSave(){
    
    
        User user = new User();
        user.setUsername("一个Java小白");
        user.setAddress("广东");
        user.setSex("男");
        user.setBirthday(new Date());

        //执行保存方法
        userDao.saveUser(user);
    }
}

注意:
  如果在destory()方法中没有通过sqlSession.commit();来提交事务的话,那么添加的记录不会写入到数据库中。
   因为我们在调用openSession()时并没有设置事务自动提交,所以最终事务会自动回滚,导致记录没有写入到数据库中。见下图在这里插入图片描述

测试结果

在这里插入图片描述

执行查找方法发现成功增加了信息:
在这里插入图片描述

  这里有人可能有疑问了,id49哪里去了呢,为什么会直接跳到50了呢。这是因为之前没有通过sqlSession.commit();来提交事务,它自动回滚,所以这也算占了一个位置。

  • 在添加用户的时候,如果我们想获取新增用户的 id 值,那么就可以使用 标签,见代码:
<!-- 添加用户,同时获取 id 的返回值 -->
<insert id="saveUser" parameterType="cn.ykf.pojo.User">
    <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO user(username,birthday,sex,address) VALUES (#{username},#{birthday},#{sex},#{address})
</insert>

  • keyProperty表示 selectKey 语句结果应该被设置的目标属性(对应实体类)。

  • keyColumn表示匹配属性的返回结果集中的列名称(对应数据库结果集)。

  • order 可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后再执行 selectKey 中的语句。

  • 测试代码及运行结果:
    /**
     * 测试增加用户操作
     */
    @Test
    public void testSave(){
    
    
        User user = new User();
        user.setUsername("一个Java小白");
        user.setAddress("广东");
        user.setSex("男");
        user.setBirthday(new Date());
        System.out.println("添加前 : " + user);

        //执行保存方法
        int count = userDao.saveUser(user);
        System.out.println("增加条数:"+count);
        System.out.println("添加后 : " + user);
    }

在这里插入图片描述

二、删除操作

在 UserDao 接口中新增 deleteUser() 方法

    /**
     * 根据 id 删除用户
     * @param userId
     * @return
     */
    int deleteUser(Integer userId);

在 Dao映射文件 UserDao.xml 中配置删除操作

    <!-- 删除用户  -->
    <delete id="deleteUser" parameterType="java.lang.Integer">
        delete from user where id = #{uid}
    </delete>

这里有两点需要注意一下
  第一点,如果参数是基本类型或者基本类型的包装类,且只有一个参数,那么参数符号可以随便写 。也就是说,虽然 Dao接口中的方法声明为int deleteUser(Integer userId),但是映射文件中既可以写 #{userId},也可以写 #{aaaa}。(意思就是随便你写什么都可以)
  第二点,parameterTypeint、INT、INTEGER、integer、java.lang.Integer都可以。原因在后面的typeAliases 标签会解释

测试删除操作

/**
     * 测试删除操作
     */
    @Test
    public void testDelete(){
    
    
        //执行删除方法
        int count = userDao.deleteUser(58);
        System.out.println("删除条数:"+count);
    }

测试结果

在这里插入图片描述

三、修改操作

在 UserDao 接口中新增 updateUser() 方法

    /**
     * 修改用户
     * @param user
     */
    int updateUser(User user);

在 Dao映射文件 UserDao.xml 中配置修改操作

    <!-- 修改用户 -->
    <update id="updateUser" parameterType="com.domain.User">
        update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}
    </update>

测试修改操作

 /**
     * 测试修改操作
     */
    @Test
    public void testUpdate(){
    
    
        User user = new User();
        // id 为自己数据库中存在的值
        user.setId(50);
        user.setUsername("一个Java小白(改)");
        user.setAddress("广东");
        user.setSex("女");
        user.setBirthday(new Date());

        //执行修改方法
        int count = userDao.saveUser(user);
        System.out.println("修改条数:"+count);
    }

测试结果

在这里插入图片描述

四、查询操作

根据ID查询

在 UserDao 中添加方法

/**
     * 根据ID查询用户信息
     * @param userId
     * @return
     */
     User findById(Integer userId);

修改映射文件

<!--  根据ID查询用户  -->
    <select id="findById" parameterType="java.lang.Integer" resultType="com.domain.User">
        select * from user where id = #{uid}
    </select>

测试代码及结果

    /**
     * 测试查询操作
     */
    @Test
    public void testFindOne(){
    
    
        //执行查询一个方法
        User user = userDao.findById(42);
        System.out.println(user);
    }

在这里插入图片描述

模糊查询

在 UserDao 中添加方法

    /**
     * 根据名称模糊查询用户信息
     * @param username
     * @return
     */
     List<User> findByName(String username);

修改映射文件

    <!-- 根据名称模糊查询 -->
    <select id="findByName" parameterType="java.lang.String" resultType="com.domain.User">
        select * from user where username like #{username}
    </select>

  虽然UserDao中方法的返回值为 List<User>,但是映射文件中 resultTypeUser就行。因为如果有多条记录的话,Mybatis 会自动帮我们封装成一个 List 集合。
  这里的parameterType 也可以直接写 String
  参数符号可以随便写。

测试代码及结果

    /**
     * 测试模糊查询操作
     */
    @Test
    public void testFindByName(){
    
    
        //执行模糊查询方法
        List<User> users = userDao.findByName("%王%");
        for (User user : users){
    
    
            System.out.println(user);
        }
    }

这样所有包含“王”字的都被查询出来了。
在这里插入图片描述

在这里插入图片描述
  我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%。配置文件中的#{username}也只是一个占位符,所以 SQL 语句显示为“?”。

模糊查询的另一种配置方式

  • 第一步:修改 SQL 语句的配置,配置如下:
<!-- 根据名称模糊查询 --> <select id="findByName" parameterType="string" resultType="com.domain.User">
 select * from user where username like '%${value}%'
</select>

我们在上面将原来的#{}占位符,改成了$ {value}。注意如果用模糊查询的这种写法,那么${value}的写法就是固定的,不能写成其它名字

  • 第二步:测试,如下:
	/**
	* 测试模糊查询操作
	 */
	@Test
	public void testFindByName(){
    
    
	 //5.执行查询一个方法
		List<User> users = userDao.findByName("王");
		for(User user : users){
    
    
			System.out.println(user);
		} 
	}

在这里插入图片描述

  可以发现,我们在程序代码中就不需要加入模糊查询的匹配符%了,这两种方式的实现效果是一样的,但执行的语句是不一样的
  那这两种方式有什么区别呢?用哪种比较好呢?
  第一种方式,采用的是Statement对象的字符串拼接SQL。
  第二种方式,采用的是PrePatedStatement的参数占位符。
  在实际开发中,我们一般使用的是第一种方式。

拓展一下:

#{}与${}的区别:
  #{}表示一个占位符号 通过#{}可以实现preparedStatement向占位符中设置值,自动进行java 类型和jdbc 类型转换,#{}可以有效防止 sql 注入。 #{}可以接收简单类型值或pojo 属性值。 如果parameterType 传输单个简单类 型值,#{}括号中可以是value 或其它名称
   $ {}表示拼接 sql 串 通过$ {}可以将 parameterType 传入的内容拼接在 sql中且不进行 jdbc类型转换,$ {}可以接收简 单类型值或 pojo属性值,如果 parameterType传输单个简单类型值,${}括号中只能是 value

使用聚合函数查询用户总数

在 UserDao 中添加方法

    /**
     * 查询总用户条数
     * @return
     */
     int findTotal();

修改映射文件

    <!-- 查询用户的总记录条数 -->
    <select id="findTotal" resultType="int">
        select count(id) from user
    </select>

测试代码及运行结果

    /**
     * 测试总记录条数
     */
    @Test
    public void testFindTotal(){
    
    
        //执行查询一个方法
        int count = userDao.findTotal();
        System.out.println("总记录条数:" +count);
    }

在这里插入图片描述

五、拓展操作

使用 pojo 包装类进行查询

  我们在上面中已经介绍了SQL语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类,现在我们来看看如何使用实体类的包装类作为参数传递。

注意:

  • 基 本 类 型String我 们 可 以直 接写 类 型 名 称 , 也 可 以 使 用 包 名 . 类 名 的 方 式 , 例 如 :java.lang.String
  • 实体类类型,目前我们只能使用全限定类名
  • 究其原因,是 mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在最后的时候我们将会了解如何注册实体类的别名。

首先介绍一下 OGNL 表达式

  • 全称 Object Graphic Navigation Language ,即对象图导航语言
  • 它是通过对象的取值方法来获取数据。在写法上把get给省略了。
    比如:我们获取用户的名称:
       类中的写法:user.getUsername();
       OGNL的写法:user.username();
  • MyBatis中为什么能直接写username,而不用user呢?
    因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名。

传递 pojo 包装对象

  开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
  Pojo 类中包含 pojo。
  需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。

编写 QueryVo 类来封装查询条件

package com.domain;
/**
 * 用于封装查询条件
 *
 */
public class QueryVo {
    
    
    private User user;

    public User getUser() {
    
    
        return user;
    }

    public void setUser(User user) {
    
    
        this.user = user;
    }
}

编写接口层代码和映射文件

    /**
     * 根据 QueryVo 中的条件查询用户
     * @param vo
     * @return
     */
    List<User> findUsersByVo(QueryVo vo);
    <!-- 根据用户名称模糊查询,参数变成一个 QueryVo 对象了 -->
    <select id="findUsersByVo" parameterType="com.domain.QueryVo" resultType="com.domain.User">
        select * from user where username like #{user.username}
    </select>
</mapper>

测试代码及运行结果

    /**
     * 测试使用QueryVo作为查询条件
     */
    @Test
    public void testFindByVo(){
    
    
        QueryVo vo = new QueryVo();
        User user = new User();
        user.setUsername("%王%");
        vo.setUser(user);
        //执行模糊查询方法
        List<User> users = userDao.findUsersByVo(vo);
        for (User u : users){
    
    
            System.out.println(user);
        }
    }

在这里插入图片描述

使用 resultMap 接收查询结果

  • resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。我们在前面已经对此属性进行过应用了。
  • 需要注意的是,它和 parameterType一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名
  • 同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法 实现封装。

但是在某些情况下,实体类的属性和数据库表的列名并不一致,那么就会出现查询结果无法封装进实体类。修改实体类进行演示:

package com.domain;

import javafx.scene.chart.PieChart;
import javafx.scene.chart.XYChart;
import java.io.Serializable;
import java.util.Date;

/**
 * @author zhang
 */
public class User implements Serializable {
    
    
	// 此时实体类属性与数据库表的列表已经不一致了
    private Integer userId;
    private String userName;
    private Date userBirthday;
    private String userSex;
    private String userAddress;

	// 此处不展示 getter()/setter()...和toString()...
	//使用快捷键Alt+Ins即可
}

修改映射文件中的参数符号,并测试原本的查询方法 testFindAll() ,运行结果如下
在这里插入图片描述

原本应该所有的属性都为 null ,但是为什么名称会有值呢?由于在 Windows 环境下 MySQL 不区分大小,所以 userName 等同username,不过要注意在 Linux 环境下 MySQL 严格区别大小写

为了解决实体类属性名与数据库表列名不一致,有以下解决方法:

  1. 在 SQL 语句中为所有列起别名,使别名与实体类属性名一致(执行效率相对高,开发效率低
    <!-- 配置查询所有   -->
    <select id="findAll" resultType="com.domain.User">
        select id as userId,username as userName,address as userAddress,sex as userSex,birthday as userBirthday from user;
    </select>
  1. 使用 resultMap 完成结果集到实体类的映射(执行效率相对低,开发效率高
<mapper namespace="com.Dao.UserDao">
    <!-- 配置查询结果的列名和实体类的属性名的对应关系
      type 属性:指定实体类的全限定类名
        id 属性:给定一个唯一标识,是给查询 select 标签引用用的。-->
    <resultMap id="userMap" type="com.domain.User">
    <!--  主键字段的对应  -->
        <id property="userId" column="id"></id>
    <!--  非主键字段的对应 -->
        <result property="userName" column="username"></result>
        <result property="userBirthday" column="birthday"></result>
        <result property="userAddress" column="address"></result>
        <result property="userSex" column="sex"></result>
    </resultMap>
    <!-- 配置查询所有   -->
    <select id="findAll" resultMap="userMap">
        <!--select id as userId,username as userName,address as userAddress,sex as userSex,birthday as userBirthday from user;-->
        select * from user;
    </select>
  • id 标签:用于指定主键字段
  • result 标签:用于指定非主键字段
  • column 属性:用于指定数据库列名
  • property 属性:用于指定实体类属性名称

六、编写Dao实现类和代理Dao的执行过程分析

这两种方式的执行过程,大家可以看看下面两张图:

编写Dao实现类的执行过程分析:
在这里插入图片描述
代理Dao的执行过程分析:
在这里插入图片描述

至于里面怎么那么多没见过的接口和类还有方法呢,大家可以自行的debug打断点尝试,然后进入其中涉及到的类或者接口当中。自定义Dao实现类的代码我就不放出来了,有兴趣的可以自行去编写。
怎么进入想进入的方法比如(代理Dao中的getMapper()方法)呢?Ctrl+左键
那怎么找这些方法的实现类呢?如图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
至于选哪个呢?就得打个断点来看了。知道之后就可以进入了
在这里插入图片描述
然后就以此类推,在里面找需要找的方法,继续打断点,直到代码执行结束

七、Mybatis 配置文件标签讲解

properties 标签

在配置数据库连接的时候,我们可以采用以下几种方式来配置:

  1. 第一种,采用全局的内部配置。采用这种方式的话,如果需要配置多个数据库环境,那么像 usernamepassword等属性就可以复用,提高开发效率。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--MyBatis的主配置文件-->
<configuration>
<!--  配置properties  -->
    <properties>
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;characterEncoding=utf-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </properties>
<!--  配置环境  -->
    <environments default="mysql">
        <!-- 配置MySQL的环境 -->
        <environment id="mysql">
        <!-- 配置事务类型 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源(连接池) -->
            <dataSource type="POOLED">
            <!-- 配置数据库的基本信息 -->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/dao/UserDao.xml"/>
    </mappers>
</configuration>
  1. 第二种,使用 resources 属性引入外部配置文件(常用

编写配置文件 jdbcConfig.properties。配置文件名没有限制,但是配置文件一定要放在类路径下

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456

上面的URL中不用& amp;而是用&就行了

修改 Mybatis 配置文件:

<properties resource="jdbcConfig.properties"/>
<!--  配置环境  -->
    <environments default="mysql">
        <!-- 配置MySQL的环境 -->
        <environment id="mysql">
        <!-- 配置事务类型 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源(连接池) -->
            <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>
  1. 第三种,使用 url 属性引入外部配置文件

该方法的外部文件可以放在任意位置,但是路径写法必须按照 Url 的方式,写起来比较麻烦,推荐第二种方法

<!-- 引入外部文件  -->
<properties url="file:///D:/document/IdeaProjects/java_web_ssm/my_mybatis/src/main/resources/jdbcConfig.properties"/>
  • URL:Uniform Resouce LOcator,即统一资源定位符。它可以唯一标识一个资源的位置,由四部分组成:协议、主机、端口、路径
  • 例如:http://localhost:8080/mybatisserver/demo1,其中 http为协议, localhost 为主机,8080 为端口号,/mybatisserver/demo1为uri(路径)
  • URI:Uniform Resource Identifier,即统一资源标识符。它是在应用中可以唯一定位一个资源的。

typeAliases 标签

之前在编写映射文件的时候,resultType 这个属性可以写int、INT 等,就是因为 Mybatis 给这些类型起了别名。Mybatis 内置的别名如表格所示:

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
Integer Integer
double Doublle
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

如果我们也想给某个实体类指定别名的时候,就可以采用 typeAliases 标签。用法如下:

<configuration>
    <!--配置别名-->
    <typeAliases>
        <typeAlias type="com.domain.User" alias="user"/>
    </typeAliases>
    <!-- 其他配置省略... -->
</configuration>
  • typeAlias 子标签用于配置别名。其中 type属性用于指定要配置的类的全限定类名(该类只能是某个domain实体类), alias 属性指定别名。一旦指定了别名,那么别名就不再区分大小写
  • 也就是说,此时我们可以在映射文件中这样写 resultType="user",也可以写 resultType="USER"

当我们有多个实体类需要起别名的时候,那么我们就可以使用 package 标签。

        <typeAliases>
            <package name="com.domain"/>
        </typeAliases>

package 标签指定要配置别名的包,当指定之后,该包下的所有实体类都会注册别名,并且别名就是类名,不再区分大小写

package 标签还可以将某个包内的映射器接口实现全部注册为映射器,如下所示

<!-- 指定映射文件 -->
<mappers>
    <package name="com.Dao"/>
</mappers>

这样配置后,我们就无需一个一个地配置 dao 接口了。不过,这种配置方式的前提是映射配置文件位置必须和dao接口的包结构相同。

猜你喜欢

转载自blog.csdn.net/weixin_43844418/article/details/113432114