文章目录
Mybatis概述
Mybatis是一个持久层框架。它封装了jdbc的操作细节,使开发者只需要关注sql语句本身。它使用了ORM思想实现结果集的封装。
ORM(Object Relational Mapping):对象关系映射,简单来说就是将数据库表与实体类对应起来,数据库表的列名与实体类的属性对应起来。
Mybatis的入门使用
1、创建maven工程并导入依赖
这一步的详细步骤不再描述,是maven很基础的用法。
但是你很有可能遇到“Error : java 不支持发行版本5”这样的报错
原因在于IDEA默认创建的maven工程在"Settings" -->" Bulid, Execution,Deployment " --> "Java Compiler "中默认的jdk版本是5,需要改成你所用的jdk版本。
同时你还需要到"Project Structure" --> “Modules” --> "Depencies"中去把jdk版本改掉。
2、创建实体类和dao的接口
实体类就是User类
dao的接口就是SQL方法
IUserDao.java
/**
* @Author Nino 2019/11/8
*
* 用户的持久层接口
*/
public interface IUserDao {
/**
* 查询所有操作
* @return
*/
List<User> findAll();
/**
* 保存用户
* @param user
*/
void saveUser(User user);
/**
* 更新用户
*
* @param user
*/
void updateUser(User user);
/**
* 删除用户
*
* @param userId
*/
void deleteUser(Integer userId);
User findById(Integer userId);
List<User> findByName(String userName);
}
3、创建Mybatis的主配置文件
SqlMapConfig.xml
这个文件头部是固定的,具体要做的是 配置Mysql的环境(注意,Mysql8以上的版本数据源连接池和以往配置有差别)和指定映射配置文件的位置(即每个dao配置的文件)
<?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>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池)-->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<!-- 注意这里是mysql.cj.jdbc -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- 注意时区的设置 -->
<property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=Asia/Shanghai&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<mapper resource="com/nino/dao/IUserDao.xml"/>
<!-- <mapper class="com.nino.dao.IUserDao"/> 此方法是注解-->
</mappers>
</configuration>
IUserDao.xml
映射配置文件
头文件是固定的。首先要做的是确认映射关系namespace
,如果类的属性名与数据库列名不一致,则创建resultMap
使他们一一对应。
接下来就是IUserDao.java
的方法名与SQL语句的映射关系。有返回值的一定要写resultType
或者resultMap
(取决于类属性与列属性是否一致);方法有输入的,一定要写parameterType
<?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.nino.dao.IUserDao">
<!-- 当类的属性和数据库属性不一致时 需要创建对应关系 -->
<!-- 配置 查询结果的列名和实体类的属性名的对应关系 -->
<resultMap id="userMap" type="com.nino.domain.User">
<!-- 主键字段的对应 -->
<id property="id" column="id"></id>
<!-- 非主键字段的对应 -->
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="username" column="username"></result>
</resultMap>
<!-- 配置查询所有 -->
<!--<select id="findAll" resultType="com.nino.domain.User">-->
<select id="findAll" resultMap="userMap">
select * from user ;
</select>
<insert id="saveUser" parameterType="com.nino.domain.User">
insert into user(username,address,sex,birthday)values (#{username},#{address},#{sex},#{birthday});
</insert>
<update id="updateUser">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
</update>
<delete id="deleteUser">
delete from user where id=#{id};
</delete>
<select id="findById" parameterType="int" resultType="com.nino.domain.User">
select * from user where id=#{id};
</select>
<select id="findByName" parameterType="string" resultType="com.nino.domain.User">
select * from user where username like #{name};
</select>
</mapper>
做完以上三步,即可对数据库进行CRUD操作了。
测试用例
在使用mybatis之前需要先做初始化(init()方法)
1、读取配置文件
2、使用SqlSessionFactoryBuilder
创建对象并使用build()
方法来创建SqlSessionFactory
工厂
3、使用工厂创建SqlSession
对象
4、使用SqlSession
对象创建dao接口的代理对象
初始化后要做的就是使用代理对象的方法了
最后我们需要提交我们的请求以及释放资源(destory()方法)。
/**
* mybatis的入门案例
*
* @Author Nino 2019/11/13
*/
public class MybatisTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
@Before
public void init() throws Exception {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
sqlSession = factory.openSession();// 如果括号内输入true,则设置为自动提交,可以免去提交的代码
userDao = sqlSession.getMapper(IUserDao.class);
}
@After
public void destroy() throws Exception {
// 不提交是不能成功插入数据的
sqlSession.commit();
// 释放资源
sqlSession.close();
in.close();
}
/**
* 测试查找操作
*
* @throws Exception
*/
@Test
public void testFindAll() throws Exception {
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}
/**
* 测试保存操作
*
* @throws ParseException
*/
@Test
public void testSave() throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd");
User user = new User("小明2号", dateFormat.parse("2000-01-13"), "男", "苏州市");
userDao.saveUser(user);
}
@Test
public void testUpdate() throws Exception{
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd");
User user = new User("小明", dateFormat.parse("2000-01-13"), "女", "扬州市");
user.setId(6);
userDao.updateUser(user);
}
@Test
public void testDelete() {
userDao.deleteUser(7);
}
@Test
public void testFindOne() {
User user = userDao.findById(6);
System.out.println(user);
}
@Test
public void testFindByName() {
List<User> users = userDao.findByName("%i%");
for (User user : users) {
System.out.println(user);
}
}
}
标签学习
properties
在SqlMapConfig.xml
中,使用<properties>
标签可以将jdbc的具体配置分离出来。
jdbcConfig.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=Asia/Shanghai&characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456
注意,properties中的&
符号不再是xml文件中转义字符,因此没必要写成&
。
相应的,SqlMapConfig.xml
将如下,可以明显的看到,在加入properties配置后,配置数据库连接的信息从具体的值变成了${名称}
<?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 resource="jdbcConfig.properties"/>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池)-->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<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>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<mapper resource="com/nino/dao/IUserDao.xml"/>
<!-- <mapper class="com.nino.dao.IUserDao"/> 此方法是注解-->
</mappers>
</configuration>
package
目的:提高开发效率
使用<typeAliases>
将实体类打包,就可以对mapper的xml中的各种type直接赋值类名且无视大小写
SqlMapConfig.xml
...
<!-- 配置别名,只能配置domain里的类 -->
<!-- 注意要写在properties的后面,environment的前面 -->
<typeAliases>
<package name="com.nino.domain"/>
</typeAliases>
...
IUserDao.xml
...
<!-- type都可以直接写user且不区分大小写 -->
<resultMap id="userMap" type="User"/>
<insert id="saveUser" parameterType="User"/>
<select id="findById" parameterType="int" resultType="User"/>
...
指定mapper接口时也可以使用package,与注释中语句的效果是一样的
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<!-- <mapper resource="com/nino/dao/IUserDao.xml"/> -->
<!-- <mapper class="com.nino.dao.IUserDao"/> 此方法是注解-->
<package name="com.nino.dao"/>
</mappers>
Mybatis的连接池、事务及动态SQL
连接池
配置连接池可以减少我们获取连接所消耗的时间
<!-- 配置数据源(连接池)-->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<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>
上述代码中的type
标签就是表示采取何种连接池方式
type
属性有以下三个值
- POOLED:采用传统的
javax.sql.DataSource
规范中的连接池 - UNPOOLED:采用传统获取连接的方式,没有使用池的思想
- JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器拿到的DataSource是不一样的
事务(面试必考!)
什么是事务
事务的四大特性ACID
不考虑隔离性会产生的3个问题
解决办法:四种隔离级别
通过SqlSession对象的
commit()
和rollback()
方法实现事务的提交和回滚
动态SQL
含义
相比较***静态SQL***语句,***动态SQL***在编码时SQL语句还不能完整地写出来, 而是在程序执行时才能构造出来,这种在程序执行临时生成的SQL 语句叫***动态SQL***语句 。
where标签
最好用where标签来代替语句中写where
,可以省去一些麻烦的事(比如必须要写1=1来避免and带来的困扰)
if 标签
接口中定义
// 通过传入的user对象的username对数据库进行查询
List<User> findByUserCondition(User user);
mapper中定义
<select id="findByUserCondition" resultMap="userMap" parameterType="user">
select * from user
<where>
<if test="username != null">
and username like #{username}
</if>
</where>
</select>
测试类中测试
@Test
public void testFindByUserCondition() {
User user = new User();
user.setUsername("%小明%");
// 使用代理对象执行方法
List<User> users = userDao.findByUserCondition(user);
for (User u : users) {
System.out.println(u);
}
}
foreach标签
用于多个查询的SQL语句,如:
select * from user where id in(1,2,3)
针对这种语句,我们可以借助辅助类QueryVo.java
,其中有个属性专门来存储id,用这种方式低耦合且方便增加或删除要查询的id
public class QueryVo {
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
}
接口中定义
// 根据QueryVo提供的id集合,查询用户信息
List<User> findByUserIds(QueryVo vo);
mapper中定义
<select id="findByUserIds" resultMap="userMap" parameterType="QueryVo">
select * from user
<where>
<if test="ids != null and ids.size()>0">
<foreach collection="ids" open="and id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>;
</select>
foreach元素的属性主要有item,index,collection,open,separator,close。
collection表示传入的需要被遍历的结合
open表示该语句以什么开始
close表示以什么结束
item表示集合中每一个元素进行迭代时的别名(可以为任意值,同时要确保与#{}中的别名保持一致)
separator表示在每次进行迭代之间以什么符号作为分隔符
index指定一个名字,用于表示在迭代过程中,每次迭代到的位置
if标签中有一个test属性,test属性值是一个符合OGNL要求的判断表达式,表达式的结果可以使true或者false,除此之外所有的非0值都为true
抽取重复的SQL语句
(我觉得有点鸡肋)
定义
<!--抽取重复的sql语句-->
<sql id="defaultUser">
select * from user
</sql>
语句
<include refid="defaultUser"></include>
实例应用
<!--抽取重复的sql语句-->
<sql id="defaultUser">
<!-- 不要用分号,拼接的时候会出事 -->
select * from user
</sql>
<!--配置查询所有 其中id不能乱写必须是dao接口中的方法 resultType写的是实体类的全路径-->
<select id="findAll" resultMap="userMap">
<include refid="defaultUser"/>
</select>
Mybatis的多表查询
SQL的JOIN
多表查询离不开SQL的JOIN语句(加不加OUTER据说都一样)
- INNER JOIN:如果表中有至少一个匹配,则返回行
- LEFT JOIN:返回左表所有的行,同时返回成功匹配到的右表的行
- RIGHT JOIN:返回右表所有的行,同时返回成功匹配到的左表的行
- FULL JOIN:只要其中有一个表中存在匹配,则返回所有的行
例句
select * from user u LEFT JOIN account a on u.id = a.UID;
一对一的关系
从表实体应该包含一个主表实体对象的引用。
例如用户和账户之间,用户可以拥有多个账户,但每个账户只属于一个用户。
账户类中需要包含一个用户对象来方便引用。
Account.java
...
private User user;
...
如此操作后,需要在mapper中定义一对一的关系映射,封装user的内容
<resultMap id="accountMap" type="Account">
<id property="id" column="aid"/>
<result property="money" column="money"/>
<result property="uid" column="uid"/>
<!-- 一对一的关系映射,配置封装user的内容,切记指定javaType-->
<association property="user" column="uid" javaType="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
<result property="address" column="address"/>
</association>
</resultMap>
注意!列名如果相同会出bug,因此有必要当建表时就取好不同的id名,或者在sql语句中写好别名
select u.*,a.ID as aid, a.UID,a.MONEY from user u LEFT JOIN account a on u.id = a.UID;
一对多的关系
一个用户拥有一堆账户,因此需要给用户类中添加一个存储账户的集合,这里我们可以取List<Account>
。
相应的mapper文件也要改动。
<resultMap id="userAccountMap" type="User">
<id property="id" column="id"/>
<result property="address" column="address"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="username" column="username"/>
<!-- 一对多的关系映射,封装accounts -->
<collection property="accounts" ofType="Account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
</collection>
</resultMap>
注意封装时一对多的集合(collection)和一对一的关系映射(association)不一样。
不一样的地方还有javaType
和ofType
。
多对多的映射关系
一个人可以有多个身份(爸爸,老师),一个身份也可以由多个人组成,这就是多对多的关系。
具体代码不再赘述,掌握思想后可以参考一对多的代码来改。思想如下:
在实现多对多的关系时,双方的类都需要包含对方实体类的集合引用。
在建表时,如果只有user表和role表,是无法表示多对多的映射关系,需要新建一个user_role的辅助表来建立连接。
user_role中uid和rid都需要设置为主键和外键。
sql多表外链接查询语句参考
select u.*, r.id as rid,r.role_name,r.role_desc from role r
left outer join user_role ur on r.id = ur.rid
left outer join user u on u.id=ur.uid
加油!虽然没有代码,但多对多的映射关系你已经懂了~
Mybatis的延迟加载
延迟加载即懒加载,如果被查询的对象有关联对象(比如:用户拥有的账户,账户所属的用户),除非需要,否则将不会主动查询关联对象的信息。
association,collection都可以实现延迟加载。
具体mapper中的代码要改写成这样
<resultMap id="userAccountMap" type="User">
<id property="id" column="id"/>
<result property="address" column="address"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="username" column="username"/>
<collection property="accounts" column="id" ofType="Account" select="com.nino.dao.IUserDao.findById">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
</collection>
</resultMap>
可以看到,collection中多了select和column属性。
select属性内容是查找关联对象的方法位置
column属性内容是给这个方法传入查询的参数
相应的sql语言将变为
<!-- select u.*,a.ID as aid, a.UID,a.MONEY from user u LEFT JOIN account a on u.id = a.UID; -->
<select id="findAll" resultMap="userAccountMap">
select * from user;
</select>
<select id="findById" resultType="Account" parameterType="int">
select * from account a where UID=#{id}
</select>
SqlMapConfig.xml中需要配置setting标签,位置在properties和typeAliases中间
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消极加载,即延迟加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
延迟加载完成,如果不主动调用用户的账户,在查询用户时是不会查询用户的账户信息的。
Mybatis的注解开发
CRUD
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
List<User> findAll();
/**
* 保存用户
* @param user
*/
@Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})")
void saveUser(User user);
/**
* 更新用户
* @param user
*/
@Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}")
void updateUser(User user);
/**
* 删除用户
* @param userId
*/
@Delete("delete from user where id=#{id} ")
void deleteUser(Integer userId);
}
多表查询
一对多的关系用many=@many
一对一的关系用one=@one
案例可见:
一对一:
public interface IAccountDao {
/**
* 查询所有账户,并且获取每个账户所属的用户信息
* @return
*/
@Select("select * from account")
@Results(id="accountMap",value = {
@Result(id=true,column = "id",property = "id"),
@Result(column = "uid",property = "uid"),
@Result(column = "money",property = "money"),
//这个注解是引入主表 FetchType(加载时机) EAGER(立即加载)
@Result(property = "user",column = "uid",one=@One(select="com.itheima.dao.IUserDao.findById",fetchType= FetchType.EAGER))
})
List<Account> findAll();
/**
* 根据用户id查询账户信息
* @param userId
* @return
*/
@Select("select * from account where uid = #{userId}")
List<Account> findAccountByUid(Integer userId);
}
一对多:
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id="userMap",value={
@Result(id=true,column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday"),
@Result(property = "accounts",column = "id",
many = @Many(select = "com.itheima.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY))
})
List<User> findAll();
/**
* 根据id查询用户
* @param userId
* @return
*/
@Select("select * from user where id=#{id} ")
@ResultMap("userMap")
User findById(Integer userId);
/**
* 根据用户名称模糊查询
* @param username
* @return
*/
@Select("select * from user where username like #{username} ")
@ResultMap("userMap")
List<User> findUserByName(String username);
}