MyBatis
1. MyBatis简介
-
MyBatis是一款优秀的持久层框架,用于简化JDBC的开发。
-
MyBatis本是 Apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
- 持久层:指的是就是数据访问层(dao),是用来操作数据库的。
- 框架:是一个半成品软件,是一套可重用的、通用的、软件基础代码模型。在框架的基础上进行软件开发更加高效、规范、通用、可拓展。
2. MyBatis简化JDBC开发
2.1 JDBC开发
2.1.1 JDBC简介
JDBC: ( Java DataBase Connectivity ),就是使用Java语言操作关系型数据库的一套API。
本质:
sun公司官方定义的一套操作所有关系型数据库的规范,即接口。
各个数据库厂商去实现这套接口,提供数据库驱动jar包(实际上就是实现sun公司接口的实现类)。
我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
通过JDBC就可以用一套Java代码操作不同的关系型数据库。
2.1.2 JDBC示例
- pom文件导入mysql驱动
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
- 利用JDBC操作数据库
public class jdbcDemo {
private static final String url = "jdbc:mysql://localhost:3306/student";
private static final String username = "root";
private static final String password = "123456";
public static void main(String[] args) throws Exception {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
Connection connection = DriverManager.getConnection(url, username, password);
// 3. 定义sql
String sql = "UPDATE primaryinfo SET age = 21 WHERE name = 'Lysssyo'";
// 4. 获取执行sql的对象
Statement statement = connection.createStatement();
// 5. 执行sql
int count = statement.executeUpdate(sql); // 返回值为受影响的行数
// 6. 处理结果
System.out.println(count);
// 7. 释放资源
statement.close();
connection.close();
}
}
Class.forName("com.mysql.jdbc.Driver");
这段代码做了这些事情:
- 加载驱动类:
Class.forName
方法动态加载指定的驱动类。在这里,加载的是com.mysql.jdbc.Driver
类,这是 MySQL 的 JDBC 驱动类。- 静态初始化块:在加载驱动类时,
com.mysql.jdbc.Driver
类的静态初始化块会被执行。这个静态初始化块会注册驱动程序实例到DriverManager
。源码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }
- 注册驱动:驱动程序的注册是通过
DriverManager
类的registerDriver
方法完成的。注册的驱动程序会被添加到DriverManager
管理的驱动程序列表中。
补充——JDBC的API
DriverManager:驱动管理类
注册驱动
上面的demo中的
Class.forName("com.mysql.jdbc.Driver");
可以看出,这段代码的底层是是通过DriverManager
类的registerDriver
方法完成驱动的注册。获取数据库连接
DriverManager
的getConnection
方法:static Connection getConnection(string url,string user,string password)
其中,参数url的语法为
jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2
示例:jdbc:mysql://127.0.0.1:3306/db1?useSSL=false
jdbc:mysql
是一个可以看作一个协议,指用jdbc连接mysql数据库参数键值对配置 useSSL=false 参数,可以禁用安全连接方式,解决警告提示
Connection:数据库连接接口
获取执行sql的对象
例如:
Statement statement = connection.createStatement();
管理事务
Statement:执行sql的接口
执行sql
例如:
int count = statement.executeUpdate(sql); // 返回值为受影响的行数
2.2 MyBatis开发
从上面JDBC的demo可以看出,通过原生JDBC连接数据库存在硬编码,操作繁琐的问题,MyBatis可以简化JDBC的开发
2.2.1 基于MyBatis
Demo:查询User表中的数据
- 导入MySql,MyBatis坐标
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
- 创建核心配置文件
<!--mybatis-config.xml-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED"> <!--使用默认的pooled数据源-->
<!--连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件-->
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
- 编写sql映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<select id="selectAll" resultType="com.lysssyo.pojo.User">
select * from user;
</select>
</mapper>
- 编码
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
// 1. 加载MyBatis核心配置文件,获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
// 通常情况下,当资源文件(比如配置文件、属性文件等)位于项目的 resources 目录下时,可以直接使用相对路径来加载
// 因为在编译后,这些资源文件会被打包到类路径下,可以通过类加载器直接访问到。
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 执行sql语句
List<User> users = sqlSession.selectList("test.selectAll");
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
}
程序可以正常运行,但是连接信息是硬编码在
mybatis-config.xml
中的,可以再写一个properties
解耦:#jdbc.properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis_test?useSSL=false jdbc.username=root jdbc.password=123456
修改
mybatis-config.xml
:<!--省略--> <configuration> <!--这里指明要加载jdbc.properties--> <properties resource="jdbc.properties"/> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!--连接信息--> <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> <mappers> <!--省略--> </mappers> </configuration>
2.2.2 基于MyBatis(Mapper代理)
以上面的demo为例,原始的基于MyBatis做开发时,在调用sqlSession
的方法selectList
时,把UserMapper.xml
的名称空间以及id当作字符串传入了方法:
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
// 1. 加载MyBatis核心配置文件,获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 执行sql语句
List<User> users = sqlSession.selectList("test.selectAll");
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
}
这个操作存在一个明显的问题,UserMapper.xml
的名称空间以及id在代码中写死,耦合度高。
Mapper代理的方法可以解决这个问题,利用Mapper代理,上面的代码可以改成这样:
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
// 1. 加载MyBatis核心配置文件,获取sqlSessionFactory对象
// 同上一样,省略
// 2.获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 执行sql语句
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
// 同上一样,省略
}
}
具体实现如下:
步骤:
- 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下
编译后的代码都在类路径(classes)下,resources的配置文件也是在类路径下:
如果想要resources中的SQL映射文件与mapper在同一目录下,只需要在resources中建与接口同样层次的包就可以了:
- 设置SQL映射文件的namespace属性为Mapper接口全限定名
<mapper namespace="com.lysssyo.mapper.UserMapper">
<select id="selectAll" resultType="com.lysssyo.pojo.User">
select * from user;
</select>
</mapper>
- 在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
public interface UserMapper {
List<User> selectAll();
}
-
编码:
-
通过 SqlSession 的 getMapper方法获取 Mapper接囗的代理对象
-
调用对应方法完成sql的执行
-
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
// 1. 加载MyBatis核心配置文件,获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 执行sql语句
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
}
如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式优化SQL映射文件的加载
前面已经做到了Mapper接口名和SQL映射文件名相同且在同一目录下,所以可以用包扫描的方式化SQL映射文件的加载,即mybatis-config.xml可以这样写:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <!--省略environment--> </environments> <mappers> <!--加载sql映射文件--> <!--<mapper resource="com/lysssyo/mapper/UserMapper.xml"/>--> <!--Mapper代理的方式--> <package name="com.lysssyo.mapper"/> </mappers> </configuration>
后面的MyBatis与Spring整合,以及MyBatis与SpringBoot整合,都是基于Mapper代理
2.2.2 MyBatis与Spring整合
- 导入Druid依赖(作为DataSource)
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
- 导入Spring,MySql,MyBatis的坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
- 导入Spring操作数据库的坐标,MyBatis与Spring整合的坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
- 编写Jdbc配置类(配置数据源)
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
#jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_test?useSSL=false
jdbc.username=root
jdbc.password=123456
- 编写MyBatis核心配置类
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setTypeAliasesPackage("com.lysssyo.pojo");
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer=new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.lysssyo.mapper");
return mapperScannerConfigurer;
}
}
与MyBatis的配置文件类比:
<configuration> <properties resource="jdbc.properties"/> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!--连接信息--> <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> <mappers> <!--Mapper代理--> <package name="com.lysssyo.mapper"/> </mappers> </configuration>
配置文件中的datasource标签是设置数据源,用注解开发管理MyBatis时,是通过形参把IOC容器中的DataSource对象引入的,从而设置datasource
mapperScannerConfigurer这个Bean对象,对应配置文件中的mappers标签
- SpringConfig引入这个两个配置类并加载jdbc.properties
@Configuration
@Import({JdbcConfig.class, MybatisConfig.class})
@PropertySource({"classpath:jdbc.properties"})
@ComponentScan("com.lysssyo")
public class SpringConfig {
}
- 测试
public class SpringDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = ctx.getBean(UserService.class);
List<User> users = userService.getAllUsers();
for (User user : users) {
System.out.println(user);
}
}
}
没有给出userService与userMapper,其实userService与userMapper的写法与SpringBoot一致
2.2.3 MyBatis与SpringBoot整合
- 导入Mysql,Springboot整合Mybatis以及Druid的依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<!-- Druid连接池依赖 -->
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
Druid数据源可以不配,不配就用默认
配置Druid数据源:
不配:
- 配置数据源
#application.yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/hm-cart
username: root
password: 123
- 编写Mapper接口
@Mapper
public interface ItemMapper {
@Select("select * from item where id = #{id}")
public List<Item> list(Integer id);
}
- 测试
@SpringBootTest
class SpringbootMybatisDemoApplicationTests {
@Autowired
private ItemMapper itemMapper;
@Test
void test(){
List<Item> itemList = itemMapper.list(317578);
System.out.println(itemList);
}
}
省略了Item类
3. 数据库连接池
3.1 数据库连接池简介
数据库连接池是个容器,负责分配、管理数据库连接(Connection)。它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。此外,数据库连接池还会释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。
-
程序在启动时,会在数据库连接池(容器)中,创建一定数量的Connection对象
-
客户端在执行SQL时,先从连接池中获取一个Connection对象,然后在执行SQL语句,SQL语句执行完之后,释放Connection时就会把Connection对象归还给连接池(Connection对象可以复用)
-
客户端获取到Connection对象了,但是Connection对象并没有去访问数据库(处于空闲),数据库连接池发现Connection对象的空闲时间 > 连接池中预设的最大空闲时间,此时数据库连接池就会自动释放掉这个连接对象
好处:
- 资源重用
- 提升系统响应速度
- 避免数据库连接遗漏
避免数据库连接遗漏是指在应用程序执行完数据库操作后,及时释放数据库连接,以确保连接池中的连接能够得到有效的重复利用,并防止数据库连接资源泄漏
3.2 数据库连接池实现
标准接口:DataSource
-
官方(SUN)提供的数据库连接池标准接口,由第三方组织实现此接口。
-
功能:获取连接
Connection getConnection()
常见的数据库连接池:
- DBCP
- C3P0
- Druid
Druid连接池是阿里巴巴开源的数据库连接池项目,功能强大,性能优秀,是Java语言最好的数据库连接池之一
利用数据库连接池,Connection对象不再通过DriverManager获取而是通过第三方实现的DataSource接口的getConnection方法获取
3.3 使用数据库连接池
3.3.1 JDBC开发使用数据库连接池
没有使用数据库连接池的情况下
public class jdbcDemo {
private static final String url = "jdbc:mysql://localhost:3306/student";
private static final String username = "root";
private static final String password = "123456";
public static void main(String[] args) throws Exception {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
Connection connection = DriverManager.getConnection(url, username, password);
// 3. 定义sql
String sql = "UPDATE primaryinfo SET age = 21 WHERE name = 'Lysssyo'";
// 4. 获取执行sql的对象
Statement statement = connection.createStatement();
// 5. 执行sql
int count = statement.executeUpdate(sql); // 返回值为受影响的行数
// 6. 处理结果
System.out.println(count);
// 7. 释放资源
statement.close();
connection.close();
}
}
而使用数据库连接池,connection
不再用DriverManager
获取,而是由Druid的DataSource获取:
// 2. 获取连接
// 2.1 加载配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/druid.properties"));
// 2.2 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 2.3 获取数据库连接 Connection
Connection connection = dataSource.getConnection();
具体实现如下:
- 导入坐标
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.1</version>
</dependency>
- 定义配置文件
#druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true
username=root
password=1234
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间(毫秒)
# 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池没有可用连接,设置-1时表示无限等待
maxWait=3000
- 测试
public class jdbcDemo {
public static void main(String[] args) throws Exception {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
// 2.1 加载配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/druid.properties"));
// 2.2 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 2.3 获取数据库连接 Connection
Connection connection = dataSource.getConnection();
// 3. 定义sql
String sql = "UPDATE primaryinfo SET age = 20 WHERE name = 'Lysssyo'";
// 4. 获取执行sql的对象
Statement statement = connection.createStatement();
// 5. 执行sql
int count = statement.executeUpdate(sql); // 返回值为受影响的行数
// 6. 处理结果
System.out.println(count);
// 7. 释放资源
statement.close();
connection.close();
}
}
3.3.2 MyBatis使用数据库连接池
参考《2.2.1 基于MyBaits》,可以发现
<!--mybatis-config.xml-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED"> <!--使用默认的pooled数据源-->
<!--连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--省略-->
</mappers>
</configuration>
<dataSource type="POOLED">
指定了使用默认连接池。可以更改这个标签使用第三方连接池
POOLED
(默认连接池)、UNPOOLED
(不使用连接池)以及第三方连接池如DBCP
,C3P0
,HikariCP
等。
<dataSource type="HIKARICP"> <!--更换为HikariCP数据源-->
<!--连接信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis_test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!-- HikariCP 相关配置 -->
<property name="maximumPoolSize" value="10"/>
<property name="minimumIdle" value="5"/>
<property name="idleTimeout" value="30000"/>
<property name="connectionTimeout" value="20000"/>
<property name="maxLifetime" value="1800000"/>
</dataSource>
注意引入对应依赖
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version> <!-- 版本号根据项目实际情况选择 -->
</dependency>
3.3.3 MyBatis与Spring整合使用数据库连接池
参考《2.2.2 MyBaits与Spring整合》,可以发现,只要导入不同的依赖,再在创建Bean
对象时new
不同的DataSource
就可以选择不同的数据源
例如:
- 导入HikariCP依赖(作为DataSource)
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.x.x</version> <!-- 根据需要调整版本号 -->
</dependency>
- 编写Jdbc配置类(配置数据源)
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
HikariDataSource dataSource = new HikariDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
#jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_test?useSSL=false
jdbc.username=root
jdbc.password=123456
其他步骤与《2.2.2 MyBaits与Spring整合》一致
3.3.4 MyBatis与SpringBoot整合使用数据库连接池
SpringBoot默认使用HikariCP数据源,更换数据源只需要导入对应的依赖,不用做额外的配置。
具体见《2.2.3 MyBaits与SpringBoot整合》
4. 实践总结
4.1 日志输出
#指定mybatis输出日志的位置, 输出控制台
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4.2 #{key}方式获取方法中的参数值
只有一个参数的情况下,#{key}
可以正常获取方法中的参数值
@Mapper
public interface ItemMapper {
@Select("select * from item where id = #{id}")
List<Item> list(Integer id);
}
但是,当方法中有两个参数时
@Mapper
public interface ItemMapper {
@Select("select * from item where id = #{id} and status =#{status}")
List<Item> list(Integer id,Integer status);
}
这是因为:
在编译时,生成的字节码文件当中,不会保留Mapper接口中方法的形参名称,而是使用var1、var2、…这样的形参名字,此时要获取参数值时,就要通过@Param注解来指定SQL语句中的参数名
修改:
方法一:使用@Param
@Mapper
public interface ItemMapper {
@Select("select * from item where id = #{id} and status =#{status}")
List<Item> list(@Param("id") Integer id, @Param("status") Integer status);//使用@Param
}
值得注意的是,在编译时,生成的字节码文件当中,不会保留Mapper接口中方法的形参名称,所以@Param内的参数不必要与方法形参保持一致(但是需要与占位的参数一致)
另外一个值得注意的是,上面的test方法改为如下,程序也可以正常运行
@Test void test(){ Integer a=317878; Integer s=1; List<Item> itemList = itemMapper.list(a, s); System.out.println(itemList); }
这说明,不要求程序中的形参与接口内的方法的形参保持一致。
方法二:参数再次封装
@Data
public class ItemVO {
Integer id;
Integer status;
}
@Mapper
public interface ItemMapper {
@Select("select * from item where id = #{id} and status =#{status}")
List<Item> list(ItemVO itemVO);
}
4.3 预编译SQL
从上面的例子可以看出,参数317578并没有在后面拼接,id的值是使用?
进行占位。那这种SQL语句我们称为预编译SQL。
预编译SQL有两个优势:
-
性能更高
预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译。(只是输入的参数不同)
-
更安全(防止SQL注入)
将敏感字进行转义,保障SQL的安全性。
在Mybatis中提供的参数占位符有两种:${…} 、#{…}
-
#{…}
- 执行SQL时,会将#{…}替换为?,生成预编译SQL,会自动设置参数值
- 使用时机:参数传递,都使用#{…}
-
${…}
- 拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题
- 使用时机:如果对表名、列表进行动态设置时使用
注意事项:在项目开发中,建议使用#{…},生成预编译SQL,防止SQL注入安全。
4.4 驼峰映射
#驼峰映射
mybatis:
configuration:
map-underscore-to-camel-case: true
表中的字段:
而实体类的参数:
表字段与实体参数不一致,执行查询时,结果无法正常返回
需要开启驼峰映射:
4.5 XML注解开发
在Mybatis中使用XML映射文件方式开发,需要符合一定的规范:
-
XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)
-
XML映射文件的namespace属性为Mapper接口全限定名一致
-
XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致。
示例:
- 写Mapper接口
@Mapper
public interface ItemMapper {
List<Item> selectById(Integer id);
}
- resource相同目录下写xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lysssyo.mapper.ItemMapper">
<select id="selectById" resultType="com.lysssyo.pojo.Item">
select * from item where id = #{id}
</select>
</mapper>
注意同包。注意namespace,id以及resultType的书写
- 测试
可以使用MyBatisX插件快速定位Mapper接口以及xml映射文件
MybatisX的安装:
4.6 主键返回
概念:在数据添加成功后,需要获取插入数据库数据的主键。
操作:标签中useGeneratedKeys
设置为true
,keyProperty
设置为实体类属性名
代码实现:
@Mapper
public interface ItemMapper {
void testInsert(Item item);
}
<mapper namespace="com.lysssyo.mapper.ItemMapper">
<insert id="testInsert" useGeneratedKeys="true" keyProperty="id">
insert into item(name,price,stock) values (#{name},#{price},#{stock})
</insert>
</mapper>
测试:
如果使用注解开发:
@Mapper public interface EmpMapper { //会自动将生成的主键值,赋值给emp对象的id属性 @Options(useGeneratedKeys = true,keyProperty = "id") @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})") public void insert(Emp emp); }
4.7 模糊查询
例如,实现这个SQL语句
SELECT * FROM item WHERE name like "%果%"
MyBatis实现一:
@Mapper
public interface ItemMapper {
List<Item> fuzzySearchByName(String name);
}
<mapper namespace="com.lysssyo.mapper.ItemMapper">
<select id="fuzzySearchByName" resultType="com.lysssyo.pojo.Item">
select * from item where name like '%${name}%'
</select>
</mapper>
可以正常返回,但是没有使用预编译SQL,存在SQL注入的风险且效率不高
MyBatis例二:
@Mapper
public interface ItemMapper {
List<Item> fuzzySearchByName(String name);
}
<mapper namespace="com.lysssyo.mapper.ItemMapper">
<select id="fuzzySearchByName" resultType="com.lysssyo.pojo.Item">
select * from item where name like concat('%',#{name},'%')
</select>
</mapper>
select * from item where name like concat('%',#{name},'%')
对应
select * from item where name like '%${name}%'
select * from item where name like concat(#{name},'%')
对应
select * from item where name like '{name}%'
4.8 动态SQL
4.8.1 if
例一:根据id
,name
,status
查询商品
@Mapper
public interface ItemMapper {
List<Item> searchByConditions(Item item);
}
<mapper namespace="com.lysssyo.mapper.ItemMapper">
<select id="searchByConditions" resultType="com.lysssyo.pojo.Item">
select * from item
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="name != null">
and name like concat('%', #{name},'%')
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
</select>
</mapper>
只有两个条件拼接两个条件
三个条件就拼接三个条件
注意,<if>
标签中的and
不能省略,因为and
在不需要时可以自动删除,但不会在需要时自动添加
删去and
时
<select id="searchByConditions" resultType="com.lysssyo.pojo.Item">
select * from item
<where>
<if test="id != null">
id = #{id}
</if>
<if test="name != null">
name like concat('%', #{name},'%')
</if>
<if test="status != null">
status = #{status}
</if>
</where>
</select>
例二:修改商品
@Mapper
public interface ItemMapper {
void updateByConditions(Item item);
}
<update id="updateByConditions">
update item
<set>
<if test="status!= null">
status = #{status},
</if>
<if test="stock != null">
stock =#{stock},
</if>
</set>
<where>
<if test="id != null">
and id =#{id}
</if>
</where>
</update>
值得注意的是,在<set>
标签中<if>
中的and
不会自动删去,而在<where>
标签中会;在<set>
标签中<if>
中的,
会自动删去,而在<where>
标签不会;
证明:
上面的Mapper接口对应的xml修改为:
<update id="updateByConditions">
update item
<set>
<if test="status!= null">
and status = #{status},
</if>
<if test="stock != null">
and stock =#{stock},
</if>
</set>
<where>
<if test="id != null">
and id =#{id},
</if>
</where>
</update>
所以,在实践中,<where>
中的<if>
前面加and
,后面不加,
;<set>
中的<if>
前面不加and
,后面加,
4.8.2 for each
如果要通过集合对数据库做批量操作,那么for each
是很好的选择
<foreach collection="集合名称" item="集合遍历出来的元素/项" separator="每一次遍历使用的分隔符"
open="遍历开始前拼接的片段" close="遍历结束后拼接的片段">
</foreach>
例一:查找给定id集合的商品
@Mapper
public interface ItemMapper {
List<Item> selectByIds(@Param("Ids") List<Integer> Ids);
}
@Param(“Ids”)不可省略
<select id="selectByIds" resultType="com.lysssyo.pojo.Item">
select * from item where id in
<foreach collection="Ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
例二:批量插入
@Mapper
public interface ItemMapper {
void insertBatch(@Param("itemList") List<Item> itemList);
}
<insert id="insertBatch">
insert into item(name,id,stock) values
<foreach collection="itemList" item="item" separator="," >
(#{item.name},#{item.id},#{item.stock})
</foreach>
</insert>
似乎for each的实践都是这两种
4.8.3 sql&include
在xml映射文件中配置的SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码
我们可以对重复的代码片段进行抽取,将其通过<sql>
标签封装到一个SQL片段,然后再通过<include>
标签进行引用。
-
<sql>
:定义可重用的SQL片段 -
<include>
:通过属性refid,指定包含的SQL片段
SQL片段: 抽取重复的代码
<sql id="commonSelect">
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp
</sql>
然后通过<include>
标签在原来抽取的地方进行引用。操作如下:
<select id="list" resultType="com.itheima.pojo.Emp">
<include refid="commonSelect"/>
<where>
<if test="name != null">
name like concat('%',#{name},'%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>