中级16 - 数据库实战:MyBatis
2020-06-02 本文已影响0人
晓风残月1994
关于 MyBatis 其实在多线程网络爬虫项目的时候,已经实战过了,这次再做一个复习和更多的了解。
首先要说的是,首选的资料是官方文档,抄官方文档是光明正大的。
1. Mybatis 是什么
⼀个 ORM (对象关系映射)框架。自动完成对象到数据库的映射。
是 Java 对象(或者你的Java代码)与数据库之间的桥梁。
可以把 Java 对象和数据库相关记录、字段等做映射、自动转换。
使用 JDBC 查询,拼接一大堆 SQL 字符串是很痛苦的、容易出错的。
2. 如何快速上手
- 看官方文档
- 首先配置日志框架,可以极大提高排查问题的效率
- 然后配置数据源
- 目录结构:Java 代码都放在 src/main/java 中,而其他的都是资源,都放在 src/main/resources 中
-
image.png
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。
- 基于注解:Mapper 接口由 SqlSession.getMapper 方法进行反射+动态代理实现。缺点是复杂SQL不方便。
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类。
- 基于 XML:可以使用 MyBatis 提供的强大功能且清晰不易犯笔误。缺点是SQL和Java代码分离(分散注意力)
在 Mybatis 中一条 SQL 语句在 Java 世界中看来,关注两个东西,参数类型和返回类型。
它们基于 Java Bean 约定(Getter/Setter),缺失的话,就扫描属性按默认策略。
- parameterType
- resultType
- Association:配合 resultMap 使用,可以实现对象套对象的高级映射、联接(具体参见下方示例)。
resultType 返回值
指定的全限定类名,或者 map 这样内置的别名,如 map。另外可以在 Mybatis 的 config 里配置自己的别名。
parameterType 传参
- 传入实体对象
- 传入临时的 map(如果不想每次都创建一个复杂的对象,而只是传递一些比如临时的参数时。键值对等价于Java Bean 的属性)
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 的灵魂
更多示例,参考下方代码。
- if
- choose(when, otherwise) 类似 switch
- foreach
- set
- script
- ...
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. 参考
- 中级07 - 数据库基本概念和SQL语言
- 项目实战 - 多线程网络爬虫与Elasticsearch新闻搜索引擎