Java 之旅

中级16 - 数据库实战:MyBatis

2020-06-02  本文已影响0人  晓风残月1994

关于 MyBatis 其实在多线程网络爬虫项目的时候,已经实战过了,这次再做一个复习和更多的了解。
首先要说的是,首选的资料是官方文档,抄官方文档是光明正大的。

1. Mybatis 是什么

⼀个 ORM (对象关系映射)框架。自动完成对象到数据库的映射。
是 Java 对象(或者你的Java代码)与数据库之间的桥梁。
可以把 Java 对象和数据库相关记录、字段等做映射、自动转换。
使用 JDBC 查询,拼接一大堆 SQL 字符串是很痛苦的、容易出错的。

2. 如何快速上手

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>
  <settings>
    <!-- 配置日志实现 -->
    <setting name="logImpl" value="LOG4J"/>
  </settings>
  <!-- 配置 Mabatis 的类名别名,简化 XML 中的全限定类名 -->
  <typeAliases>
    <typeAlias alias="User" type="com.github.hcsp.mybatis.entity.User"/>
    <typeAlias alias="Order" type="com.github.hcsp.mybatis.entity.Order"/>
    <typeAlias alias="Goods" type="com.github.hcsp.mybatis.entity.Goods"/>
  </typeAliases>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="org.h2.Driver"/>
        <property name="url" value="jdbc:h2:file:./target/test"/>
        <property name="username" value="root"/>
        <property name="password" value="Jxi1Oxc92qSj"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="db/mybatis/MyMapper.xml"/>
  </mappers>
</configuration>

log4j 日志实现之属性配置:
主要是开启 DEBUG 模式,方便开发排查。

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
log4j.logger.com.github.hcsp.mybatis.UserDao$UserMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

3. Mapper

Mapper 有两种:基于注解和基于 XML。

Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类。

在 Mybatis 中一条 SQL 语句在 Java 世界中看来,关注两个东西,参数类型和返回类型。
它们基于 Java Bean 约定(Getter/Setter),缺失的话,就扫描属性按默认策略。

resultType 返回值

指定的全限定类名,或者 map 这样内置的别名,如 map。另外可以在 Mybatis 的 config 里配置自己的别名。

parameterType 传参

String resource = "db/mybatis/config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
    new SqlSessionFactoryBuilder().build(inputStream);

// 传入实体对象
User user = new User();
user.setId(3);
user.setName("wang");

// 传入临时的 map
HashMap<String, Object> param = new HashMap<>();
param.put("name", "Tom");

try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    // 这里第二个参数传入对象,作为 SQL 查询的参数
    System.out.println(sqlSession.selectList("com.hcsp.UserMapper.selectUser", param));

    //            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //            System.out.println(mapper.getUsers());
}

参数的 #{} 和 ${} 对比

看一下分别使用二者时的日志,使用了 JDBC 的 PrepareStatement。

<mapper namespace="com.hcsp.UserMapper">
  <select id="selectUser" resultType="User">
    select * from User where name=#{name}
  </select>
</mapper>
DEBUG [main] - ==>  Preparing: select * from User where name=? 
DEBUG [main] - ==> Parameters: wang(String)

而且 SQL 中如果要使用字符串,则要添加单引号,这样的字符串拼接、替换,就像 Statement 一样,不安全,带来了 SQL 注入的风险。

<select id="selectUser" resultType="User">
  select * from User where name='${name}'
</select>
DEBUG [main] - ==>  Preparing: select * from User where name='wang'

4. 动态 SQL 是 MyBatis 的灵魂

更多示例,参考下方代码。

foreach

// 把 map 作为参数传入 sqlSession
map.put("ids", Arrays.asList(1, 2, 3, 4, 5, 6));
<select id="selectIds" resultType="map">
  SELECT *
  FROM USER
  WHERE ID in
  <foreach item="item" index="index" collection="ids"
           open="(" separator="," close=")">
    #{item}
  </foreach>
</select>
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE ID in ( ? , ? , ? , ? , ? , ? ) 
DEBUG [main] - ==> Parameters: 1(Integer), 2(Integer), 3(Integer), 4(Integer), 5(Integer), 6(Integer)
DEBUG [main] - <==      Total: 4

5. 实战

作为参考,下次写的时候,可以快速重拾。

看一下 Mapper.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="MyMapper">
    <select id="getUserByPage" resultType="User">
        select id, name, tel, address
        from USER
        <if test="username != null">
            where name = #{username}
        </if>
        limit #{offset}, #{limit}
    </select>

    <select id="countUser" resultType="int">
        select count(*)
        from USER
        <if test="username != null">
            where name = #{username}
        </if>
    </select>

    <insert id="batchInsertUsers">
        insert into USER (name, tel, address)
        values
        <foreach item="user" collection="users" separator=",">
            (#{user.name}, #{user.tel}, #{user.address})
        </foreach>
    </insert>

    <update id="updateUser">
        update USER
        <set>
            <if test="name != null">name=#{name},</if>
            <if test="tel != null">tel=#{tel},</if>
            <if test="address != null">address=#{address},</if>
        </set>
        where id = #{id}
    </update>

    <select id="selectUserById" resultType="User">
        select id, name, tel, address
        from USER
        where id = #{id}
    </select>

    <delete id="deleteUserById">
        delete from USER where id = #{id}
    </delete>

    <select id="getInnerJoinOrders" resultMap="OrderMap">
        select
            o.id as order_id,
            (o.goods_num * o.goods_price) as total_price,
            u.id as user_id,
            u.name as user_name,
            u.tel as user_tel,
            u.address as user_address,
            g.id as goods_id,
            g.name as goods_name,
            g.price as goods_price
        from "ORDER" o
            inner join GOODS g on o.goods_id = g.id
            inner join USER u on o.user_id = u.id
        where g.name is not null and u.name is not null
    </select>

    <resultMap id="OrderMap" type="Order">
        <result property="id" column="order_id"/>
        <result property="totalPrice" column="total_price"/>
        <association property="user" javaType="User">
            <result property="id" column="user_id"/>
            <result property="name" column="user_name"/>
            <result property="tel" column="user_tel"/>
            <result property="address" column="user_address"/>
        </association>
        <association property="goods" javaType="Goods">
            <result property="id" column="goods_id"/>
            <result property="name" column="goods_name"/>
            <result property="price" column="goods_price"/>
        </association>
    </resultMap>
</mapper>

一个 UserDao

package com.github.hcsp.mybatis;

import com.github.hcsp.mybatis.entity.Pagination;
import com.github.hcsp.mybatis.entity.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.HashMap;
import java.util.List;

/**
 * 与用户有关的增删改查操作
 */
public class UserDao {
    private final SqlSessionFactory sqlSessionFactory;

    public UserDao(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    /**
     * 根据传入的参数查找用户名为username的用户,返回分页后的结果。
     *
     * @param username 传入的用户名
     * @param pageSize 分页搜索,每页显示的条数
     * @param pageNum  分页的页码,从1开始
     * @return 查找结果,若username为null,则返回所有用户的列表
     */
    public Pagination<User> getUserByPage(String username, int pageSize, int pageNum) {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            final HashMap<String, Object> param = new HashMap<>();
            param.put("username", username);
            param.put("offset", (pageNum - 1) * pageSize);
            param.put("limit", pageSize);
            List<User> users = session.selectList("MyMapper.getUserByPage", param);

            int count = session.selectOne("MyMapper.countUser", username);
            int totalPage = (count % pageSize == 0) ? count / pageSize : count / pageSize + 1;
            return Pagination.pageOf(users, pageSize, pageNum, totalPage);
        }
    }

    /**
     * 向数据库中批量插入若干个用户,注意,请使用批处理操作。
     *
     * @param users 待插入的用户列表
     */
    public void batchInsertUsers(List<User> users) {
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            HashMap<String, Object> param = new HashMap<>();
            param.put("users", users);
            session.insert("MyMapper.batchInsertUsers", param);
        }
    }

    /**
     * 根据用户的ID更新一个用户的数据,更新传入的user中所有不为null的字段。
     *
     * @param user 要修改的用户信息,其id必须不为null
     */
    public void updateUser(User user) {
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            session.update("MyMapper.updateUser", user);
        }
    }

    /**
     * 删除一个用户。
     *
     * @param id 待删除的用户ID
     */
    public void deleteUserById(Integer id) {
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            session.delete("MyMapper.deleteUserById", id);
        }
    }

    /**
     * 根据ID获取一个用户,如果该用户不存在,返回null
     *
     * @param id 待获取的用户ID
     * @return 对应的用户
     */
    public User selectUserById(Integer id) {
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            return session.selectOne("MyMapper.selectUserById", id);
        }
    }

    public static void main(String[] args) throws IOException {
        String resource = "db/mybatis/config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        UserDao userDao = new UserDao(sqlSessionFactory);
    }
}

一个 OrderDao

package com.github.hcsp.mybatis;

import com.github.hcsp.mybatis.entity.Order;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

public class OrderDao {
    private final SqlSessionFactory sqlSessionFactory;

    public OrderDao(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    /**
     * 查询订单信息,只查询用户名、商品名齐全的订单,即INNER JOIN方式
     * 结果请参考:https://github.com/hcsp/practise-select-sql/blob/a450e2a/src/main/java/com/github/hcsp/sql/Sql.java#L172
     *
     * @return 所有的订单信息
     */
    public List<Order> getInnerJoinOrders() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            return session.selectList("MyMapper.getInnerJoinOrders");
        }
    }
}

User

package com.github.hcsp.mybatis.entity;
/** 一个用户 */
public class User {
    private Integer id;
    private String name;
    private String tel;
    private String address;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTel() {
        return tel;
    }

    public void setTel(String tel) {
        this.tel = tel;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{"
                + "id="
                + id
                + ", name='"
                + name
                + '\''
                + ", tel='"
                + tel
                + '\''
                + ", address='"
                + address
                + '\''
                + '}';
    }

    public User() {}

    public User(Integer id, String name, String tel, String address) {
        this.id = id;
        this.name = name;
        this.tel = tel;
        this.address = address;
    }
}

Goods

package com.github.hcsp.mybatis.entity;

import java.math.BigDecimal;

/** 一件商品 */
public class Goods {
    private Integer id;
    private String name;
    private BigDecimal price;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + '}';
    }
}

Order

package com.github.hcsp.mybatis.entity;

import java.math.BigDecimal;

/** 一个订单 */
public class Order {
    private Integer id;
    /** 订单中的用户 */
    private User user;
    /** 订单中的商品 */
    private Goods goods;
    /** 订单中的总成交金额 */
    private BigDecimal totalPrice;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public User getUser() {
        return user;
    }

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

    public Goods getGoods() {
        return goods;
    }

    public void setGoods(Goods goods) {
        this.goods = goods;
    }

    public BigDecimal getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(BigDecimal totalPrice) {
        this.totalPrice = totalPrice;
    }

    @Override
    public String toString() {
        return "Order{"
                + "id="
                + id
                + ", user="
                + user
                + ", goods="
                + goods
                + ", totalPrice="
                + totalPrice
                + '}';
    }
}

Pagination

package com.github.hcsp.mybatis.entity;

import java.util.List;

/** 数据库的分页结果, */
public class Pagination<T> {
    private final List<T> items;
    private final int pageSize;
    private final int pageNum;
    private final int totalPage;

    private Pagination(List<T> items, int pageSize, int pageNum, int totalPage) {
        this.items = items;
        this.pageSize = pageSize;
        this.pageNum = pageNum;
        this.totalPage = totalPage;
    }

    public List<T> getItems() {
        return items;
    }

    public int getPageSize() {
        return pageSize;
    }

    public int getPageNum() {
        return pageNum;
    }

    public int getTotalPage() {
        return totalPage;
    }

    public static <T> Pagination<T> pageOf(
            List<T> items, int pageSize, int pageNum, int totalPage) {
        return new Pagination<>(items, pageSize, pageNum, totalPage);
    }

    @Override
    public String toString() {
        return "Pagination{"
                + "items="
                + items
                + ", pageSize="
                + pageSize
                + ", pageNum="
                + pageNum
                + ", totalPage="
                + totalPage
                + '}';
    }
}

6. 参考

上一篇 下一篇

猜你喜欢

热点阅读