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 内置的别名如表格所示:

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
IntegerInteger
doubleDoublle
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

如果我们也想给某个实体类指定别名的时候,就可以采用 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接口的包结构相同。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值