前言:
自学ssm已经一段时间了,深刻感受到跟着视频学必须集中一整块时间。如果断断续续地看真的会忘光前面的知识,从而后面的也连不上,整个感受就会很不系统不扎实。
今天刚结束Spring框架的学习,准备复习一下Mybatis框架。以下为个人跟着视频内容整理,有错误请一定指出!勿喷,谢谢!
视频来源:B站:BV1mE411X7yp
黑马程序出品不会错!!!强推~(黑马快打钱 x)
Mybatis概述
-
作用:Mybatis是持久层框架 -> 内部封装jdbc(省去手动加载驱动、创建连接、创建statement等繁杂过程),使开发者只需要关注sql本身。
-
持久层技术解决方案:
· JDBC技术:Connection PreparedStatement ResultSet
·Spring的JdbcTemplate:Spring中对jdbc的简单封装
·Apache的DBUtils:类似JdbcTemplate
·后两者都是工具类。
-
方式:通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
-
ORM思想:Object Relational Mapping(对象关系映射),即把数据库表和实体类及实体类属性对应起来,通过操作实体类来操作db。Mybatis框架利用ORM思想实现结果集的封装。
-
在Mybatis中CRUD一共有四个注解:@Select @Insert @Update @Delete
xml v.s. 注解
xml:集中、好维护,但较为繁琐
注解:简便(写得少),但因其较为分散所以不好维护
e.g.:@Select这一个注解就可以代替了原本xml配置方法中需要在resources文件夹底下新建com.demo.dao.IUserDao.xml创建映射关系这一步。
实战1(基于注解的单表CRUD操作)
Mysql新建数据库和表:
0.总体结构:
其中domain中的User.java就是实体类,此处实体类内部的属性名与db表中列名一一对应(实际上会有命名不统一的情况,处理方法将在实践2中提到)。
1.pom.xml 导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>mybatis_day04_annotation</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!--驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<!--日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.主配置文件:SqlMapConfig.xml
<?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>
<!-- 引入外部配置文件-->
<properties resource="jdbcConfig.properties"></properties>
<!--配置别名-->
<typeAliases>
<package name="com.itheima.domain"></package>
</typeAliases>
<!-- 配置环境-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!-- 指定带有注解的dao接口所在位置 -->
<mappers>
<mapper class="com.itheima.dao.IUserDao"></mapper>
</mappers>
</configuration>
这里导入外部配置文件就是在另一个专门的.properties文件里配置数据源信息以便修改:
在SqlMapConfig.xml中就可以直接用 ${变量名} 来指代具体信息。妙!
3.导入配置文件:log4j.properties
log4j.rootLogger=debug, stdout, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log
log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=5
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
4. 持久层接口: IUserDao.java
5.测试类:AnnotationCRUDTest
package com.itheima.test;
import com.itheima.dao.IUserDao;
import com.itheima.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;
public class AnnotationCRUDTest {
private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;
@Before
public void init()throws Exception{
in = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}
@After
public void destroy()throws Exception{
session.commit();
session.close();
in.close();
}
@Test
public void testSave(){
User user = new User();
user.setUsername("mybatis annotation");
user.setAddress("北京市昌平区");
userDao.saveUser(user);
}
@Test
public void testUpdate(){
User user = new User();
user.setId(4);
user.setUsername("mybatis annotation update");
user.setAddress("北京市海淀区");
user.setSex("男");
user.setBirthday(new Date());
userDao.updateUser(user);
}
@Test
public void testDelete(){
userDao.deleteUser(4);
}
@Test
public void testFindOne(){
User user = userDao.findById(1);
System.out.println(user);
}
@Test
public void testFindByName(){
// List<User> users = userDao.findUserByName("%mybatis%");
List<User> users = userDao.findUserByName("mybatis");
for(User user : users){
System.out.println(user);
}
}
@Test
public void testFindTotal(){
int total = userDao.findTotalUser();
System.out.println(total);
}
}
注解@Before @After @Test:
@Before:初始化方法 对于每一个测试方法都要执行一次(注意与BeforeClass区别,后者是对于所有方法执行一次)
@After:释放资源 对于每一个测试方法都要执行一次(注意与AfterClass区别,后者是对于所有方法执行一次)
@Test:测试方法,在这里可以测试期望异常和超时时间
@Before和@After注解下的代码 相当于 如下代码,没学注解时是这样实现的:
实战2(基于注解的多表一对多查询)
-
新加实体类:Account.java,实现两表:User&Account多对一(即一个账户只能属于一个用户,一个用户可拥有多个账户,在mybatis中称为一对一)的查询
数据库新表Account:
domain包中新加新的实体类Account:
为了实现多对一关系,需要在Account实体类中创建User对象~
同时在User类中也要补充Account对象作为属性!
-
实践1中预留了一个悬念:mybatis中如果实体类属性和db表中列明命名不一致怎么解决?
为测试这一点,改动原本domain中的User类:
对比数据库表User表列名:
此时运行,打印结果将会是:
User{userId=null, userName=‘xxx’, userBirthday=null, userSex=‘null’, userAddress=‘null’}
User{userId=null, userName=‘xxxxx’, userBirthday=null, userSex=‘null’, userAddress=‘null’}
只有username这一列有数据,其他列数据都是null!----->说明命名不一致会映射失败,同时也说明Mybatis大小写不区分。
解决思路:通过注解添加映射!
解决方法:在@Select等后添加注解@Results及二级注解@Result
@Results(id="这一组映射的名称",value = {
@Result(id=true,column = "db表中列名",property = "实体类中属性名"),//id=true 主键(此id代表是是否是主键,默认值是 false,非主键可省略)
@Result(property = "accounts",column = "id",many=@Many(select = "com.demo.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY))
}
- 等会说以下这行哈~
@Result(property = "accounts",column = "id",many=@Many(select = "com.demo.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY))
并且:不需要每次@Select都写一遍!!只需要 @ResultMap(value={“要用到的那组映射的名称”})即可!!
具体见下述代码:
持久层接口:IUserDao.java
package com.demo.dao;
import com.demo.domain.Account;
import com.demo.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;
import java.util.List;
public interface IUserDao {
//查询所有
@Select("select * from user")
@Results(id="userMap",value = {
@Result(id=true,column = "id",property = "userId"), //id=true 主键
@Result(column = "username",property = "userName"),
@Result(column = "birthday",property = "userBirthday"),
@Result(column = "sex",property = "userSex"),
@Result(column = "address",property = "userAddress"),
@Result(property = "accounts",column = "id",many=@Many(select = "com.demo.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY))
})
List<User> findall();
//查询部分
@Select("select * from user where id=#{id}")
@ResultMap(value={"userMap"})
User findById(Integer userId);
/*@ResultMap相当于复制了一边上面的@Results
若此处无@ResultMap 查询打印的结果同上,部分属性未能被封装
上一句也可直接写为: @ResultMap("userMap")
value在注解中如果是唯一的一个属性则可以省略 若还有别的则要加上(参照上满@Result)
若集合元素中只有一个元素 则可以省略{}
*/
//根据名字模糊查询
@Select("select * from user where username like '%${username}%'")
@ResultMap(value={"userMap"})
List<User> findUserByName(String username);
@Select("select * from account where uid = #{userId}")
@ResultMap(value={"userMap"})
List<Account> findAccountByUid(Integer userId);
}
相应地去写持久层接口:IAccountDao.java和Account测试类:AccountCRUDTest,上代码:
IAccountDao.java中也用到@Results和@Result,
- 同样的,这行一会儿和上面那行一起解释~
@Result(property = "user",column = "uid",one=@One(select="com.demo.dao.IUserDao.findById",fetchType =FetchType.EAGER))
package com.demo.dao;
import com.demo.domain.Account;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;
import java.util.List;
public interface IAccountDao {
@Select("select * from account")
@Results(id="accountMap",value = {
@Result(id=true,column = "id",property = "id"), //id=true 主键
@Result(column = "uid",property = "uid"),
@Result(column = "money",property = "money"),
@Result(property = "user",column = "uid",one=@One(select="com.demo.dao.IUserDao.findById",fetchType =FetchType.EAGER))
})
List<Account> findall();
/*One2Many:一个人可以有多个账号 但一个账号只对应一个人 这里账号对人是一对一() 人对账号是一对多
fetchtype:延迟查询LAZY:查询结果为多条 立即查询EAGER:查询结果为1条*/
@Select("select * from account where uid = #{userId}")
@ResultMap(value={"accountMap"})
List<Account> findByUid(Integer userId);
}
AccountCRUDTest:
package com.demo.test;
import com.demo.dao.IAccountDao;
import com.demo.domain.Account;
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.List;
public class AccountCRUDTest {
private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IAccountDao accountDao;
@Before
public void init() throws Exception{
in = Resources.getResourceAsStream("SqlMapConfig.xml");
factory= new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
accountDao = session.getMapper(IAccountDao.class);
}
@After
public void destroy() throws Exception{
session.commit();
session.close();
in.close();
}
@Test
public void testfindAll(){
List<Account> accounts = accountDao.findall();
for(Account account:accounts){
System.out.println("----每个账户的信息----");
System.out.println(account);
System.out.println(account.getUser());
}
}
}
此时运行测试方法会报错:
org.apache.ibatis.binding.BindingException: Type interface com.demo.dao.IAccountDao is not known to the MapperRegistry.
MapperRegistry->映射注册,新加的IAccountDao没有在配置文件里映射注册!!!->很简单,在主配置文件SqlMapConfig.xml中修改dao接口配置:(两个接口在同一个包下)
- 现在讲刚刚遗留的两个小点:
回到@Result注解,ctr+左击查看源码,发现还有One和Many:
再点进去看One的底层实现,有select和fetchtype两个属性:
点进Many也是一样的:
进一步:看FetchType底层,发现有三个属性。
LAZY: 延迟加载(对多)
EAGER: 立即加载(对一)
DEFAULT:从上面两者选一个
—>>注解可以帮助实现多对一查询,不需要写在sql里实现!!!
在Account的@Results中,在一一对应好列名和属性名后需要加一行:
@Result(property = "user",column = "uid",one=@One(select="com.demo.dao.IUserDao.findById",fetchType =FetchType.EAGER))
property:对应封装的user属性 column:用Account的uid属性查询
select:填写能实现该查询功能的方法的全限定类名
fetchType:EAGER 因为是Account在这是“一“,若是User(多)则用 LAZY
同时,在User的@Results中也需要加一行:
@Result(property = "accounts",column = "id",many=@Many(select = "com.demo.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY))
这样一来就可以通过注解达到多表查询的效果啦!
【画图大师上线:代码怎么运作的】
打开弹幕一看,企业大多都用xml…因为更清晰明了。懂了,下一期:Mybatis框架(2)–xml