使用springboot整合mybatis使用
数据库的出现就是为了将数据进行持久化,而不是简单的将其保存在内存中,因为一旦断电或者是数据需要转移,这是无法实现的。
所以数据库的出现就是为了将其保存到数据库中,方便再次进行使用。
mybatis无法像hibernate映射那么完整,但是仍然是一个关系型数据库。也被称之为是ORM框架,但是和hibernate相比,无法实现全自动,因为对于hibernate来说,不需要管SQL语句怎么编写,其会来做自动映射。但是mybatis需要手动来进行映射,而且需要编写对应的SQL语句,但是这种却更加方便我们的自定义化操作,方便修改SQL以及与pojo类和数据库表中的字段进行映射。
官方文档定义:
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
数据库版本和驱动对应
https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-versions.html
Connector/J 5.1 is a Type 4 pure Java JDBC driver, which conforms to the JDBC 3.0, 4.0, 4.1, and 4.2 specifications. It provides compatibility with all the functionality of MySQL, including 5.6, 5.7 and 8.0.
我的服务器使用的是MySQL版本是8.0.26,属于8.0版本的,所以可以使用MySQL数据库驱动5.1的。
可以看到,我这里可以使用5.1的,也可以使用8.0的,但是因为有jdk版本要求。本地使用8,无影响。所以随便选择。
一些简单的复制操作:
1、找到驱动的完整限定类型
2、对应的URL
上面这张图有两个作用对于现在来说。
1、测试是否可以正常连接到数据库;
2、拷贝URL路径;
可能还能生成对应的实体类之类的操作,但是现在是不需要这样子来进行操作的。
从 XML 中构建 SqlSessionFactory
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
给文件起个名字,然后将里面的配置信息改一改即可。
那么此时做完上面的修改配置,就已经具备了连接数据库的能力了。那么接下来可以来进行其他的操作。
一般来说,操作顺序是创建数据库表所对应的实体类,然后创建出来对应的mapper接口。就这么两个步骤。
那么接着去官网来进行操作
首先创建实体类:
public class Emp {
private Long id;
private String name;
// 省略get/set方法
}
创建对应的mapper.xml文件,这里需要注意的是,如果使用了maven,那么需要将其创建在resource目录下,在编译后的target目录下,可以看到EmpMapper.xml和对应的EmpMaaper接口是在同一级目录下,所以使用了maven之后,在resource目录下新建和EmpMapper文件相同的路径,打包后会编译到相同路径下。如果没有使用maven,那么可以将EmpMapper.xml直接建立在相同目录下。在springboot中可以自定义mapper对应的xml文件所对应的文件目录所在的位置。
注 :xml文件中的namespace要填写mapper接口对应的相对路径,、、、标签中的ID和namespace联合组成的称之为statementId,来作为唯一标识。
对应的mapper文件:
<mapper namespace="com.guang.mybatis.mapper.EmpMapper">
<select id="findEmpById" resultType="com.guang.mybatis.pojo.Emp" parameterType="int">
select * from emp where id = #{id}
</select>
</mapper>
对应的测试类:
@Test
public void testTwo() throws IOException {
// 通过xml文件来获取得到对应的SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取得到一个sqlsession对象,这个会话对象是用来和数据库来进行交互的
try (SqlSession session = sqlSessionFactory.openSession()) {
EmpMapper mapper = session.getMapper(EmpMapper.class);
Emp emp = mapper.findEmpById(1);
System.out.println(emp);
}
}
通过加载配置文件来获取得到SqlSessionFactory,这个是一个重量级别的操作,所以开启一次即可。刚刚好符合我们的单例模式
所以将其放入到容器中来使用即可。
获取得到SqlSessionFactory来解析配置文件,将配置文件中的信息加载到configuration对象中去,方便后期来进行理解和维护。
sqlsession对象字面意思是执行SQL的对象,说明这个是会话级别的操作。使用完成之后应该给予关闭操作。也就是,操作完成之后关闭即可。
这个sqlsession可以看一下官方文档说明介绍:
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。
使用 MyBatis 的主要 Java 接口就是 SqlSession。你可以通过这个接口来执行命令,获取映射器实例和管理事务。在介绍 SqlSession 接口之前,我们先来了解如何获取一个 SqlSession 实例。SqlSessions 是由 SqlSessionFactory 实例创建的。SqlSessionFactory 对象包含创建 SqlSession 实例的各种方法。而 SqlSessionFactory 本身是由 SqlSessionFactoryBuilder 创建的,它可以从 XML、注解或 Java 配置代码来创建 SqlSessionFactory。
官方文档Java Api文档中有说明这个问题。
默认的 openSession() 方法没有参数,它会创建具备如下特性的 SqlSession:
事务作用域将会开启(也就是不自动提交)。
将由当前环境配置的 DataSource 实例中获取 Connection 对象。
事务隔离级别将会使用驱动或数据源的默认设置。
预处理语句不会被复用,也不会批量处理更新。
给opensession方法设置不同的参数会影响到对于数据库的操作,最常见的就是在增删改的时候,如果不设置,那么默认是没有事务的,但是如果设置了,那么就根据对应的条件来进行设置即可。
SqlSession openSession() // 手从提交事务
SqlSession openSession(boolean autoCommit) // 自动提交事务
SqlSession openSession(Connection connection) // 自己设置数据库连接
SqlSession openSession(TransactionIsolationLevel level) // 事务隔离级别
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType) // 执行器类型。可以用来设置预处理语句,将参数解析成参数
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();
执行器类型:
ExecutorType.SIMPLE:该类型的执行器没有特别的行为。它为每个语句的执行创建一个新的预处理语句。
ExecutorType.REUSE:该类型的执行器会复用预处理语句。
ExecutorType.BATCH:该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新中间执行,将在必要时将多条更新语句分隔开来,以方便理解。
介绍一下全局配置文件:
在configuraion类中,有以下的几个数据库实现:
this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
点进去对应的源码,那么这里我们可以来进行参考对应的配置类即可。
github自动生成:https://github.com/mybatis/generator
对应的文档链接:http://mybatis.org/generator/
导入对应的pom文件:
<?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>com.guang</groupId>
<artifactId>mybatis-two</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
</project>
对应的配置文件是:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--数据库驱动-->
<classPathEntry location="D:\mysql-connector-java-8.0.27.jar"/>
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--数据库链接地址账号密码-->
<jdbcConnection
driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"
userId="root" password="123456">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成Model类存放位置-->
<javaModelGenerator targetPackage="com.guang.mybatis.model" targetProject="src\main\java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!--生成映射文件存放位置-->
<sqlMapGenerator targetPackage="com.guang.mybatis.mapper" targetProject="src\main\resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!--生成Dao类存放位置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.guang.mybatis.mapper" targetProject="src\main\java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--生成对应表及类名-->
<table tableName="emp" domainObjectName="Emp"></table>
</context>
</generatorConfiguration>
1、入门案例
在idea中创建springboot项目,勾选mybatis和jdbc
首先看下pom.xml依赖:
# 应用名称
spring.application.name=springboot-mybatis-test-one
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件。从这里可以看到以后的mappers文件都需要写在类路径下的mappers文件夹下
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.guang.springbootmybatistestone.mybatis.entity
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址。默认的地址和数据库表名
spring.datasource.url=jdbc:mysql://localhost:3306/blue?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=***
spring.datasource.password=***
# 需要查看打印出来的sql语句
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
在上面填写自己的数据库IP地址和对应的表名以及账号密码即可。
编写对应的实体类:
@Data
public class Account {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
编写对应的接口:
public interface AccountDao {
/**
* 查询所有的账号
* @return
*/
List<Account> findAllAccount();
}
这里需要注意的是因为没有在接口上添加注解,该接口将会根据动态代码来生成对应的代理类,也需要注册到容器中来。
所以需要在接口上添加注解,但是因为接口在后续中需要不断的来进行添加,所以直接来启动类上加上注解来扫描制定路径下的包即可
@SpringBootApplication
@MapperScan("com.guang.springbootmybatistestone.mybatis.dao")
public class SpringbootMybatisTestOneApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisTestOneApplication.class, args);
}
}
编写接口对应的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">
<!--namespace:给哪个接口配置的映射,写接口的全限定类名-->
<mapper namespace="com.guang.springbootmybatistestone.mybatis.dao.AccountDao">
<!--select标签:表示要执行查询语句; id:给接口里哪个方法配置的,写方法名;resultType:结果集封装类型-->
<select id="findAllAccount" resultType="com.guang.springbootmybatistestone.mybatis.entity.Account">
select * from account
</select>
</mapper>
那么来进行测试:
@Autowired
private AccountDao accountDao;
@Test
public void testfindAllAccount(){
List<Account> allAccount = accountDao.findAllAccount();
allAccount.forEach(System.out::println);
}
因为在上面添加了打印日志,所以直接在控制台上来进行查看:
==> Preparing: select * from user
==> Parameters:
<== Columns: id, username, birthday, sex, address
可以看到打印出来的SQL语句。
2、条件查询
2.1、方法参数
2.1.2、方法参数只有一个
有以下几种类型。
基本类型、pojo类、map、list
其实如果只有一个的话,推荐使用@Param注解在接口方法中直接指定在ongl表达式中的填写的内容。
基本类型
看接口:
/**
* 根据用户Id来进行查询
* @param userId
* @return
*/
User findUserById(@Param("id") Integer userId);
对应的xml文件:
<select id="findUserById" parameterType="int" resultType="user">
select * from user where id = #{id}
</select>
测试一下:
@Test
public void testfindUserById(){
User user = accountDao.findUserById(42);
log.info("对应的user对应的数据是-------->>>>>>>{}",user);
}
查看控制台打印即可。
这里想说的是@Param("id")中注解的value值就是我们在xml文件中,#{}中所写的内容。至于这里是为什么,那么之后也可以来进行补充说明。但是推荐的是使用的时候都给加上。
pojo类型
接口:
/**
* 根据用户来进行条件查询
* @param user
* @return
*/
List<User> findAllAccountByName(@Param("user") User user);
对应的xml
<select id="findAllAccountByName" parameterType="user" resultType="user">
select * from user where username like #{user.username} and sex = #{user.sex}
</select>
这里就不在来写测试类了,本地测试通过了,就不在上来进行粘贴了。
map类型
接口:
/**
* 通过map来进行查询用户
* @param map
* @return
*/
User findUserByMap(@Param("hellomap") Map map);
对应的xml文件
<select id="findUserByMap" resultType="user" parameterType="map">
select * from user where username = #{hellomap.name} and address = #{hellomap.add}
</select>
在测试的时候,需要注意的事情是,map中的key要和xml文件中#{}中填充的保持一致,因为我在接口中对map进行了重新定义,所以也需要来补充上。
@Test
public void testfindUserByMap(){
Map<String,Object> map = new HashMap<>();
map.put("name","tony");
map.put("add","北京");
User user = accountDao.findUserByMap(map);
log.info("user对应的数据是:-------->>>>> {}",user);
}
list类型
/**
* 通过ID集合来查询满足条件的用户
* @param idList
* @return
*/
List<User> findUserByIdList(@Param("ids") List<Integer> idList);
在这里需要使用到mybatis中的标签来进行使用。
对应的xml文件
<select id="findUserByIdList" parameterType="list" resultType="user">
select * from user
<where>
<foreach collection="ids" open="id in (" close=")" separator="," item="id" index="i">
#{id}
</foreach>
</where>
</select>
其中,需要说明的是,index指的是下标,而不是里面的元素的值。item是每个下标的值
2.1.3、方法参数多个
将方法参数中只有一个的类型中的类型进行组装即可。
其他的方法和方法参数是一个的差不了多少。
2.2、方法返回值
从上面的测试中可以看到,无论返回是一个pojo还是一个list,返回值填写的都是一个pojo类型的。
原因是因为不管是list还是其他的类型,返回的时候都会是一个类型。只不过mybatis底层来进行判断了而已。如果只是一个,那么就取出来一个;如果是多个,那么才会取出来多个。
在以前的jdbc操作中,从数据库查询出来的是一行一行的记录。但是现在在mybatis中可以自动将查询出来的数据给封装到对象中来。
这一步mybatis来给实现好了。所以这里很有必要了解一下。在mybatis中,需要将查询出来的字段和pojo类中的属性名称保持一致。
在之前的学习中,我们有一种方式来解决这个问题。那就是通过给查询出来的字段起别名。
还有一种可能也会用到的操作,那么就是开启驼峰命名操作。但是这种操作是受限的。因为如果是数据库中的表中字段就只是这种方式,那么开启这种方式就可以了。
但是mybatis中提供了一种简便的方法来进行操作。就是resultmap,强大的映射操作。尽管数据库中的字段和pojo类中的属性名称不同,通过映射也是可以将其封装好的。
在标签中,还有一些比较好用的操作。
3、关联查询
表跟表之间的关联关联通常来说分为:一对一、一对多、多对多
现在主流的两种方式,一种是类引用方式,还有另外一种是嵌套查询。个人倾向于嵌套查询,因为这样子这样子写起来很舒服。
那么分别来测试一下这两种方式,然后结合着resultMap来使用一下。
最佳操作:在进行操作之前,首先判断是一对一、一对多、多对多中的哪种关系。
然后需要自己手写出来对应的sql语句出来。
需求一:查询所有帐户表信息,及其关联的用户信息
一个账号对应着的是一个用户。一对一关系,那么应该在account类添加一个字段user类型的
下面我用嵌套查询来写了。
对应如下:
@Data
public class Account {
private Integer id;
private Integer uid;
private Double money;
private User user;
}
对应的接口:
public interface AccountDao {
/**
* 查询账号关联的用户数据
* @return
*/
List<Account> findAllAccountAndUser();
}
User类:
@Data
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
对应的接口方法:
/**
* 根据用户Id来进行查询
* @param userId
* @return
*/
User findUserById(@Param("id") Integer userId);
在AccountDao.xml配置的有:
<mapper namespace="com.guang.springbootmybatistestone.mybatis.dao.AccountDao">
<select id="findAllAccountAndUser" resultMap="findAllAccountAndUserMap">
select * from account
</select>
<resultMap id="findAllAccountAndUserMap" type="account">
<id property="id" column="id"/>
<result property="uid" column="uid" />
<result property="money" column="money"/>
<association property="user" javaType="user" select="com.guang.springbootmybatistestone.mybatis.dao.UserDao.findUserById" column="uid"/>
</resultMap>
</mapper>
从上面可以看到,因为是类引用方式,而且前面不是List类型,而是简单的关联查询而已。所以使用的是association,javaType。
通过select来选择使用哪个dao中的哪个方法,需要传递的参数是什么,通过column来进行传递。
需求二:查询所有用户(user)信息,以及每个用户拥有的所有帐号(account)信息
那么先分析出来是一对多的关系,那么说明需要在User表中建立list类型的属性。
@Data
public class Account {
private Integer id;
private Integer uid;
private Double money;
}
@Data
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<Account> accountList;
}
写出来对应的接口:
public interface UserDao {
List<User> findAllUser();
}
对应的xml文件:
<select id="findAllUser" resultMap="findAllUserResultMap">
select * from user
</select>
<resultMap id="findAllUserResultMap" type="user">
<id 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="accountList" ofType="account"
select="com.guang.springbootmybatistestone.mybatis.dao.AccountDao.findAccountById"
column="id"/>
</resultMap>
AccountDao接口:
public interface AccountDao {
/**
* 通过ID来进行查询对应的account
* @param id
* @return
*/
Account findAccountById(@Param("id") Integer id);
}
对应的xml文件:
<select id="findAccountById" parameterType="int" resultType="account">
select * from account where uid = #{id}
</select>
然后将对应的记录查询出来即可。
从这里可以看到如果类中的属性是集合数据类型,那么使用的是collection,对应的数据类型是ofType
需求三:现有用户表(user)和角色表(role),是多对多关系。有中间关系表user_role
那么这个就是用来练手的。
一个用户有多个角色,一个角色对应着多个用户。多对多的关系。
这种表应该先从中间表来进行查询
# 第一步:查询中间表
select * from user_role where uid = 41;
# 第二步:指定一个具体的ID
select rid from user_role where uid = 41;
# 第三步:从要查询的表中来进行查询
select * from role;
select * from role where id in (select rid from user_role where uid = 41);
# 第四步
select u.*,r.id rid,r.ROLE_NAME rolename,r.ROLE_DESC description from user u left join user_role ur on u.id = ur.UID left join role r on ur.rid = r.ID;
这就是查询出来的数据
那么开始建立起来对应的类:
@Data
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<Role> roleList;
}
@Data
public class Role {
private Integer id;
private String roleName;
private String roleDesc;
private List<User> users;
}
写出来对应的接口即可:
public interface UserDao {
List<User> findAllUser();
}
对应的xml文件是:
<select id="findAllUser" resultMap="findAllUserResultMap">
select u.*,r.id rid,r.ROLE_NAME rolename,r.ROLE_DESC description
from user u left join user_role ur
on u.id = ur.UID
left join role r on ur.rid = r.ID
</select>
<resultMap id="findAllUserResultMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<collection property="roleList" ofType="role">
<id property="id" column="rid"/>
<result property="roleName" column="rolename"/>
<result property="roleDesc" column="description"/>
</collection>
</resultMap>
写一个测试类直接来进行测试即可。
需求四:查询所有角色,及关联的用户集合