MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.5version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.6version>
dependency>
sqlMapConfig.xml:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis_db"/>
<property name="username" value="root"/>
<property name="password" value="2552696563"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
mappers>
configuration>
或者用spring去整合
applicationContext.xml:
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${db.driverClassName}">property>
<property name="url" value="${db.url}">property>
<property name="username" value="${db.username}">property>
<property name="password" value="${db.password}">property>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="configLocation" value="classpath:sqlMapConfig.xml">property>
bean>
<bean id="mapperScan" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory">property>
<property name="basePackage" value="com.qcby.dao">property>
bean>
通常我们将写 sql 的 xml 放在 resources/mapper 目录下。
一般一个 dao 对应一个 mapper
namespace要对应对应接口的
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qcby.dao.UserDao">
mapper>
基本不用,知道就可以。
public interface UserMapper {
@Select("SELECT id, username, email FROM users WHERE id = #{id}")
@Results({
@Result(property = "userId", column = "id"),
@Result(property = "userName", column = "username"),
@Result(property = "emailAddress", column = "email")
})
User getUserById(int id);
}
<configuration>
<mappers>
<package name="com.qcby.dao"/>
mappers>
configuration>
首先,基本是sql要会编写。
https://blog.csdn.net/Cosmoshhhyyy/article/details/133383339?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170666553716800215042990%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=170666553716800215042990&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-1-133383339-null-null.nonecase&utm_term=%E6%95%B0%E6%8D%AE%E5%BA%93&spm=1018.2226.3001.4450
假设有这么个类:
com.qcby.model.User:
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
......
}
public User findById(int id);
<select id="findById" resultType="com.qcby.model.User" parameterType="int">
select * from user where id = #{id};
select>
public int insert(User user);
<insert id="insert" parameterType="com.qcby.model.User">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select last_insert_id();
selectKey>
insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
insert>
public int update(User user);
<update id="update" parameterType="com.qcby.model.User">
update user set username = #{username},birthday = #{birthday},sex = #{sex},address=#{address} where id = #{id}
update>
public void delete(Integer userId);
<delete id="delete" parameterType="int">
delete from user where id = #{id};
delete>
#{} 占位符:
${} 占位符:
在sqlMapConfig加上:
<typeAliases>
<package name="com.qcby.model"/>
typeAliases>
这样我们parameterType 和 resultType中就不用写完整的路径
像 com.qcby.model.User 可以直接写错user
假设有个类:
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<Integer> ids;
........
}
根据用户名和性别进行模糊查询。
public List<User> findByIf(User user);
<select id="findByIf" parameterType="user" resultType="user">
select * from user
<where>
<if test="username != null and username != ''" >
AND username like #{username}
if>
<if test="sex != null and sex != ''">
AND sex = #{sex}
if>
where>
select>
查询id 为 user里list ids中所有用户
public List<User> findByForeach(User user);
<select id="findByForeach" parameterType="user" resultType="user">
select * from user
<where>
<foreach collection="ids" open="id in (" close=")" separator="," item="i">
#{i}
foreach>
where>
select>
select * from user where id = 1 or id = 2 or id = 3 or id = 4
使用 refid 的好处在于,它可以让你在多个地方重用相同的 SQL 片段,当 SQL 片段需要修改时,只需要修改一处,不用在多个地方做同样的修改,提高了配置的可维护性和可读性。
<sql id="findAll">
select * from user
sql>
<select id="findAll" resultType="com.qcby.model.User">
<include refid="findAll" />
<where>
......
where>
select>
例如:
<sql id="selectColumns">
id, username, email
sql>
<select id="getUserById" parameterType="int" resultType="User">
SELECT
<include refid="selectColumns"/>
FROM users
WHERE id = #{userId}
select>
<choose>, <when>, <otherwise>
<select id="selectUserByCondition" parameterType="User" resultType="User">
SELECT * FROM users
<where>
<choose>
<when test="username != null">
AND username = #{username}
when>
<when test="email != null">
AND email = #{email}
when>
<otherwise>
AND status = 'ACTIVE'
otherwise>
choose>
where>
select>
<trim>
<select id="selectUsers" parameterType="Map" resultType="User">
SELECT * FROM users
<trim prefix="WHERE" prefixOverrides="AND | OR">
<if test="username != null">
AND username = #{username}
if>
<if test="email != null">
AND email = #{email}
if>
trim>
select>
可以用在set,update
<update id="updateUser" parameterType="User">
UPDATE users
<set>
<trim suffixOverrides=",">
<if test="username != null">
username = #{username},
if>
<if test="email != null">
email = #{email}
if>
trim>
set>
WHERE id = #{id}
update>
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
......
}
public class Account {
private Integer id;
private Integer uid;
private Double money;
......
}
多个账户 对应 一个用户
在多的一方添加对方属性
这里再acccount 添加 user 属性。
public class Account {
private Integer id;
private Integer uid;
private Double money;
private User user; // 这里
......
}
现在查询所有账户信息和其拥有者
public List<Account> findAll();
<select id="findAll" resultMap="accountMap">
select a.*,u.username,u.address from account a,user u where a.uid = u.id
select>
<resultMap id="accountMap" type="com.qcby.model.Account">
<result property="id" column="id" />
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<association property="user" javaType="com.qcby.model.User">
<result property="username" column="username"/>
<result property="address" column="address"/>
association>
resultMap>
一个用户对应多个账号
在一的一方,加上多的一方的对象集合
这里user加上account的集合list
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<Account> accounts; // 这里
......
}
查询所有用户及其所有账户金额
public List<User> findOneToMany();
<select id="findOneToMany" resultMap="userMap">
select u.*,a.money from user u left join account a on u.id = a.uid
select>
<resultMap id="userMap" type="com.qcby.model.User">
<result property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="accounts" ofType="com.qcby.model.Account">
<result property="money" column="money"/>
collection>
resultMap>
和一对多相同
在自己类上加上对方的集合
或者再对方上面加上自己的集合
例如有一个角色类, 一个用户有多个角色,一个角色可以有多个用户。
public class Role {
private Integer id;
private String role_name;
private String role_desc;
private List<User> users; // 这里
.......
}
public List<Role> findAllRole();
<select id="findAllRole" resultMap="roleMap">
SELECT r.*,u.username FROM USER u,user_role ur,role r WHERE u.id = ur.UID AND ur.RID = r.ID
select>
<resultMap id="roleMap" type="role">
<result property="id" column="id"/>
<result property="role_name" column="role_name"/>
<result property="role_desc" column="role_desc"/>
<collection property="users" ofType="user">
<result property="username" column="username"/>
collection>
resultMap>
无非是两种方法:
其实就是根据查询的角度来查询,和实际生活是对应的。
而数据库不会因为这个变化,比如无论是一对多、还是多对一,数据库都是多的一方把一的一方的主键作为外键。
而多对多都是建立再建立一个表。
我们的数据库结构从始至终都没有变化就可以印证这一点。所以不要弄混。
延迟加载(Lazy Loading)是一种在对象关系映射(ORM)框架中常见的优化技术,用于提高性能并减少资源消耗。它的核心思想是:不在对象初始化时加载其关联的数据,而是在访问相关属性时才触发数据的加载。
延迟加载的优点:
例如:
在查账户时,一个账户对应一个用户,可以直接把用户信息查出来,就用直接加载。
再查用户时,一个用户对应多个账户,可能我们用不到账户信息暂时,等用到的时候再去加载,就利用延迟加载。
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
多对一:
<select id="findAll" resultMap="accountMap">
SELECT * from account
select>
<select id="findByUid" parameterType="int" resultType="account">
select * from account where uid = #{uid}
select>
<resultMap type="Account" id="accountMap">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<association property="user" javaType="User" select="com.qcby.dao.UserMapper.findById" column="uid">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="addresss" column="addresss"/>
association>
resultMap>
一对多:
<select id="findAll" resultMap="userMap">
select * from user
select>
<select id="findByUid" parameterType="int" resultType="account">
select * from account where uid = #{uid}
select>
<resultMap type="user" id="userMap">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="sex" property="sex"/>
<result column="addresss" property="addresss"/>
<collection property="accounts" ofType="Account" select="com.qcby.dao.AccountMapper.findByUid" column="id" >
<id column="id" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
collection>
resultMap>
无非就是把我们之前写的多表查询中的collection 或则 association 对应的sql提取了出来,将select拆分成两个select, 单独设置了一个select方法,只有用到的时候才去调用。
当然多了一个column属性,作为拎出来的方法参数,例如多对一中的通过用户的id查询用户信息, 一对多中的通过账户的id查询账户信息。
一级缓存(Local Cache):
一级缓存是 MyBatis 默认开启的本地缓存,它位于 SqlSession 的生命周期内,即在同一个 SqlSession 中执行的多个查询可以共享同一个缓存。一级缓存是基于对象引用的,当执行相同的查询语句时,MyBatis 会将查询结果缓存到一级缓存中。
一级缓存的作用范围是 SqlSession,当 SqlSession 关闭时,缓存也会被清空。
二级缓存(Global Cache):
二级缓存是 MyBatis 的全局缓存,它可以被多个 SqlSession 共享。当多个 SqlSession 执行相同的查询语句时,MyBatis 将查询结果缓存到二级缓存中,下次相同的查询可以直接从缓存中获取结果。
二级缓存的作用范围是整个应用程序,因此可以在不同的SqlSession 之间共享数据。
顺便讲一下测试方法如何写。
同一sqlSession下,调用两次findById:
/**
* 测试缓存
* @throws Exception
*/
@Test
public void run5() throws Exception {
// 加载配置文件
InputStream in = Resources.getResourceAsStream("SqlMapperConfig.xml");
// 创建工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 构建session
SqlSession session = factory.openSession();
// 通过session创建rMapper接口的代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findById(1);
System.out.println(user); // com.qcby.model.User@2a70a3d8
User user2 = mapper.findById(1);
System.out.println(user2); // com.qcby.model.User@2a70a3d8
session.close();
in.close();
}
不同sqlSession下,调用两次findById:
/**
* 测试缓存
* @throws Exception
*/
@Test
public void run5() throws Exception {
// 加载配置文件
InputStream in = Resources.getResourceAsStream("SqlMapperConfig.xml");
// 创建工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 构建session
SqlSession session = factory.openSession();
// 通过session创建rMapper接口的代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findById(1);
System.out.println(user);
session.close();
SqlSession session2 = factory.openSession();
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.findById(1);
System.out.println(user2);
session2.close();
in.close();
}
可以发现,再同一session下,两次输出同一对象,而再不同session下,两次输出不同对象。
证明了一级缓存存在,且声明周期是随session。
mybatis配置文件中:
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
要使用二级缓存的mapper中:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
注意实现对应类的序列化接口
/**
* 测试缓存
* @throws Exception
*/
@Test
public void run5() throws Exception {
// 加载配置文件
InputStream in = Resources.getResourceAsStream("SqlMapperConfig.xml");
// 创建工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 构建session
SqlSession session = factory.openSession();
// 通过session创建rMapper接口的代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findById(1);
System.out.println(user);
session.close();
SqlSession session2 = factory.openSession();
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.findById(1);
System.out.println(user2);
session2.close();
in.close();
}
实现Cache接口:
package com.example;
import org.apache.ibatis.cache.Cache;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class MyCustomCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public MyCustomCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
readWriteLock.writeLock().lock();
try {
cache.put(key, value);
} finally {
readWriteLock.writeLock().unlock();
}
}
@Override
public Object getObject(Object key) {
readWriteLock.readLock().lock();
try {
return cache.get(key);
} finally {
readWriteLock.readLock().unlock();
}
}
@Override
public Object removeObject(Object key) {
readWriteLock.writeLock().lock();
try {
return cache.remove(key);
} finally {
readWriteLock.writeLock().unlock();
}
}
@Override
public void clear() {
readWriteLock.writeLock().lock();
try {
cache.clear();
} finally {
readWriteLock.writeLock().unlock();
}
}
@Override
public int getSize() {
return cache.size();
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
}