本文主要是介绍关于Mybatis和JDBC的使用及区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《关于Mybatis和JDBC的使用及区别》:本文主要介绍关于Mybatis和JDBC的使用及区别,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教...
基于Java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
1、JDBC
1.1、流程
下面是一个简单的示例,演示如何使用 JDBC 直接与数据库进行交互:
1. Maven 依赖(如果使用 Maven)
如果您的项目使用 Maven,确保在 pom.xml
中包含 JDBC 驱动的依赖,例如使用 mysql:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> <!-- 根据您需要的版本选择 --> </dependency>
2. 加载数据库驱动和管理 SQL
import java.sql.*; public class JdbcExample { public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { // 1. 加载数据库驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 替换为相应驱动 // 2. 建立数据库连接 String url = "jdbc:mysql://localhost:3306/yourdatabase"; // 数据库 URL String user = "yourusername"; // 数据库用户名 String password = "yourpassword"; // 数据库密码 connection = DriverManager.getConnection(url, user, password); // 3. 创建 PreparedStatement String sql = "SELECT * FROM users WHERE id = ?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setLong(1, 1); // 设置参数 // 4. 执行查询 resultSet = preparedStatement.executeQuery(); // 5. 处理结果 while (resultSet.next()) { System.out.println("User ID: " + resultSet.getInt("id")); System.out.println("User Name: " + resultSet.getString("name")); System.out.println("User Email: " + resultSet.getString("email")); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { // 6. 关闭资源 try { if (resultSet != null) resultSet.close(); if (preparedStatement != null) preparedStatement.close(); if (connection != null) connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
从以上代码展示,可以看到jdbc的流程:
- 加载数据库驱动:加载与所使用数据库对应的 JDBC 驱动。
- 建立数据库连接:使用
DriverManager.getConnection()
方法建立与数据库的连接。 - 创建
Statement
或PreparedStatement
:用于执行 SQL 查询或更新。 - 执行 SQL 语句:通过
executeQuery()
或executeUpdate()
方法执行 SQL 语句。 - 处理结果:处理查询结果 (如果有)。
- 关闭资源:关闭
ResultSet
、Statement
、Connection
等资源,防止资源泄漏。
1.2、优缺点
1.优点:
- 直接灵活:可以完全控制 SQL 语句和数据库交互的细节。
- 简单:适合小型应用或简单查询。
2.缺点:
- 冗长:代码冗长,尤其是在处理事务和异常时,容易导致代码重复。
- 不易维护:SQL 语句与业务逻辑混杂,导致难以维护和管理。
- 手动管理:需要手动处理资源的关闭,容易出现资源泄漏。
- 缺乏抽象:不支持对象关系映射,处理复杂数据结构时不够方便。
因此基于以上的现象,mybatis或者Hibernate就可以满足更复杂的要求,且统一管理,便于维护。
2、Mybatis
2.1、执行流程
2.2、使用
下面是一个使用 MyBatis 连接 MySQL 数据库的完整示例。这个示例展示了如何使用 MyBatis 进行基本的 CRUD 操作,同时使用 MySQL 作为数据库。
项目结构:
mybatis-mysql-example
│
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ ├── Main.java
│ │ │ ├── User.java
│ │ │ ├── UserMapper.java
│ │ │ └── MyBatisUtils.java
│ │ └── resources
│ │ ├── mybatis-config.xml
│ │ └── mapper
│ │ └── UserMapper.xml
└── pom.xml
1. Maven 依赖
确保在 pom.xml
中添加 MyBatis 和 MySQL 的 JDBC 驱动的依赖:
<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.example</groupId> <artifactId>mybatis-mysql-example</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>11</java.version> <mybatis.version>3.5.7</mybatis.version> <mysql.version>8.0.25</mysql.version> <!-- 使用适合你的版本 --> </properties> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2. 创建用户实体类(User.java)
package com.example; public class User { private Long id; private String username; private String email; // Getters 和 Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
3. 创建 Mapper 接口(UserMapper.java)
package com.example; import org.apache.ibatis.annotations.*; import java.util.List; @Mapper public interface UserMapper { void insertUser(User user); User getUser(Long id); List<User> getAllUsers(); List<User> getUsersByPage(@Param("offset") int offset, @Param("limit") int limit); // 分页查询 void updateUser(User user); void deleteUser(Long id); }
4. 创建 MyBatis 配置文件(mybatis-config.xml)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org/DTD Mapper 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/yourdatabase?useSSL=false&serverTimezone=UTC"/> <property name="username" value="yourusername"/> <property name="password" value="yourpassword"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration>
5. 创建 Mapper XML 文件(UserMapper.xml)
在 src/main/resources/mapper
目录下创建 UserMapper.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"> <mapper namespace="com.example.UserMapper"> <insert id="insertUser" parameterType="com.example.User"> INSERT INTO users (username, email) VALUES (#{username}, #{email}) </insert> <select id="getUser" resultType="com.example.User" parameterType="long"> SELECT * FROM users WHERE id = #{id} </select> <select id="getAllUsers" resultType="com.example.User"> SELECT * FROM users </select> <select id="getUsersByPage" resultType="com.example.User"> SELECT * FROM users LIMIT #{limit} OFFSET #{offset} <!-- 分页查询 --> </select> <update id="updateUser" parameterType="com.example.User"> UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id} </update> <delete id="deleteUser" parameterType="long"> DELETE FROM users WHERE id = #{id} </delete> </mapper>
6. 创建 MyBatis 实用工具类(MyBatisUtils.java)
package com.example; import org.apache.ibatis.session.SqlSession; import orgmgIpm.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream; public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { // 读取 mybatis-config.xml 配置文件 InputStream inputStream = MyBatisUtils.class.getClassLoader().getResourceAsStream("mybatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); } }
7. 创建主类(Main.java)
这是程序的入口,用于执行 CRUD 操作:
您可以创建一个服务类来处理业务逻辑,包括分页查询:
package com.example; import org.apache.ibatis.session.SqlSession; import java.util.List; public class UserService { public List<User> getUsersByPage(int pageNumber, int pageSize) { SqlSession sqlSession = MyBatisUtils.getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); int offset = (pageNumber - 1) * pageSize; // 计算偏移量 return userMapper.getUsersByPage(offset, pageSize); // 调用分页方法 } finally { sqlSession.close(); // 关闭 SQL 会话 } } }
调用分页查询方法并打印结果:
package com.example; import org.apache.ibatis.session.SqlSession; import java.util.List; public class Main { public static void main(String[] args) { // Initialize MyBatis and data UserService userService = new UserService(); // 进行分页查询 int pageNumber = 1; // 页码 int pageSize = 5; // 每页条数 python List<User> users = userService.getUsersByPage(pageNumber, pageSize); // 打印结果 System.out.println("Users on Page " + pageNumber + ":"); for (User user : users) { System.out.println("- " + user.getUsername()); } } }
其他接口可以使用以下方法查询:
package com.example; import org.apache.ibatis.session.SqlSession; import java.util.List; public class Main { public static void main(String[] args) { SqlSession sqlSession = MyBatisUtils.getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 插入用户 User user = new User(); user.setUsername("john_doe"); user.setEmail("john@example.com"); userMapper.insertUser(user); sqlSession.commit(); // 查询用户 User retrievedUser = userMapper.getUser(1L); System.out.println("Retrieved User: " + retrievedUser.getUsername()); // 查询所有用户 List<User> users = userMapper.getAllUsers(); System.out.println("All Users:"); for (User u : users) { System.out.println(u.getUsername()); } // 更新用户 user.setEmail("john_doe_updated@example.com"); userMapper.updateUser(user); sqlSession.commit(); // 删除用户 userMapper.deleteUser(1L); sqlSession.commit(); } finally { sqlSession.close(); // 确保资源关闭 } } }
8. 创建 MySQL 数据库和表
在 MySQL 数据库中,您需要创建数据库和用户表。假设您创建了一个名为 yourdatabase
的数据库,执行以下 SQL 语句:
CREATE DATABASE yourdatabase; USE yourdatabase; CREATE TABLE users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(100), email VARCHAR(100) );
9、运行程序。
2.3、实现方式
1、xml配置文件
通过 .xml
文件定义 SQL 语句和映射关系,与 Mapper 接口绑定。
<!-- UserMapper.xml --> <select id="getUserById" resultType="User"> SELECT * FROM user WHERE id = #{id} </select>
2、注解
直接在 Mapper 接口的方法上使用注解(如 @Select
、@Update
)编写 SQL。
@Select("SELECT * FROM user WHERE id = #{id}") User getUserById(Long id);
3、动态 SQL
早期 MyBatis 注解对动态 SQL 支持较弱,但通过 @SelectProvider
、@UpdateProvider
或者xml方式等注解,现已能实现复杂逻辑。
XML 动态标签
通过 XML 中的 <if>
、<choose>
、<foreach>
等标签实现条件逻辑。
代码示例:
<select id="searchUsers"> SELECT * FROM user <where> <if test="name != null">AND name = #{name}</if> <if test="role != null">AND role = #{role}</if> </where> </select>
注解结合 Provider 类
使用 @SelectProvider
、@UpdateProvider
注解,调用工具类生成动态 SQL:
代码示例:
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUserByName") User getUserByName(@Param("name") String name); class UserSqlBuilder { public String buildGetUserByName(Map<String, Object> params) { return new SQL() {{ SELECT("*"); FROM("user"); WHERE("name = #{name}"); if (params.get("role") != null) { AND().WHERE("role = #{role}"); } }}.toString(); } }
4、MyBatis-Plus 扩展实现
基于 MyBatis 的增强工具,提供通用 Mapper(BaseMapper
)和 ActiveRecord 模式,减少手写 SQL。
// 继承 BaseMapper 直接调用内置方法 public interface UserMapper extends BaseMapper<User> {} // 使用 ActiveRecord 模式 User user = new User(); user.setName("John"); user.insert();
总结
3、缓存
缓存的查找顺序:二级缓存 => 一级缓存 => 数据库。
3.1. 一级缓存
一级缓存是SqlSession级别的缓存,默认开启,默认是 SESSION 级
1.原理
每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。
当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。
Local Cache 其实是一个 hashmap 的结构:
private Map<Object, Object> cache = new HashMap<Object, Object>();
2.核心流程
- 第一次查询:从数据库读取数据,存入一级缓存。
- 第二次相同查询:直接读取缓存,不访问数据库。
- 执行更新操作:清空一级缓存,后续查询重新访问数据库。
代码示例:
// 示例代码 public static void main(String[] args) { SqlSessionFactory factory = ...; // 获取 SqlSessionFactory try (SqlSession session = factory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); System.out.println("--- 第一次查询 ---"); User user1 = mapper.getUserById(1L); // 查询数据库 System.out.println("查询结果: " + user1); System.out.println("--- 第二次相同查询 ---"); User user2 = mapper.getUserById(1L); // 从一级缓存读取 System.out.println("查询结果: " + user2); System.out.println("user1 == user2? " + (user1 == user2)); // 输出 true System.out.println("--- 执行更新操作 ---"); user1.setName("NewName"); mapper.updateUser(user1); // 清空一级缓存 session.commit(); // 提交事务 System.out.println("--- 第三次查询 ---"); User user3 = mapper.getUserById(1L); // 重新查询数据库 System.out.println("查询结果: " + user3); } }
预期输出:
--- 第一次查询 ---
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <== Total: 1
查询结果: User(id=1, name=OldName)--- 第二次相同查询 ---
查询结果: User(id=1, name=OldName)
user1 == user2? true // 直接从缓存获取,无 SQL 日志--- 执行更新操作 ---
DEBUG [main] - ==> Preparing: UPDATE user SET name = ? WHERE id = ?
DEBUG [main] - ==> Parameters: NewName(String), 1(Long)
DEBUG [main] - <== Updates: 1--- 第三次查询 ---
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <== Total: 1
查询结果: User(id=1, name=NewName)
关键点
- 两次查询结果相同:第二次未触发 SQL,直接返回缓存对象(
user1 == user2
)。 - 更新操作后缓存失效:第三次查询重新访问数据库。
3.缓存失效
不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
3.2. 二级缓存
二级缓存是NameSpace级别(Mapper)的缓存,多个SqlSession可以共享,使用时需要进行配置开启,同一个namespace影响同一个cache。
1.执行顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存。
代码示例:
public static void main(String[] args) { SqlSessionFactory factory = ...; // 已开启二级缓存 // 第一个会话(查询并提交) try (SqlSession session1 = factory.openSession()) { UserMapper mapper1 = session1.getMapper(UserMapper.class); System.out.println("--- SqlSession1 查询 ---"); User user1 = mapper1.getUserById(1L); // 查询数据库 System.out.println("查询结果: " + user1); session1.commit(); // 提交后同步到二级缓存 } // 第二个会话(相同查询) try (SqlSession session2 = factory.openSession()) { UserMapper mapper2 = session2.getMapper(UserMapper.class); System.out.println("--- SqlSession2 查询 ---"); User user2 = mapper2.getUserById(1L); // 从二级缓存读取 System.out.println("查询结果: " + user2); } // 第三个会话(更新数据) try (SqlSession session3 = factory.openSession()) { UserMapper mapper3 = session3.getMapper(UserMapper.class); System.out.println("--- SqlSession3 更新 ---"); User user = new User(1L, "UpdatedName"); mapper3.updateUser(user); // 清空二级缓存 session3.commit(); } // 第四个会话(重新查询) try (SqlSession session4 = factory.openSession()) { UserMapper mapper4 = session4.getMapper(UserMapper.class); System.out.println("--- SqlSession4 查询 ---"); User user4 = mapper4.getUserById(1L); // 重新查询数据库 System.out.println("查询结果: " + user4); } }
预期输出:
--- SqlSession1 查询 ---
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG [main]China编程 - ==> Parameters: 1(Long)
DEBUG [main] - <== Total: 1
查询结果: User(id=1, name=OldName)--- SqlSession2 查询 ---
查询结果: User(id=1, name=OldName) // 无 SQL 日志,直接读二级缓存--- SqlSession3 更新 ---
DEBUG [main] - ==> Preparing: UPDATE user SET name = ? WHERE id = ?
DEBUG [main] - ==> Parameters: UpdatedName(String), 1(Long)
DEBUG [main] - <== Updates: 1--- SqlSession4 查询 ---
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <== Total: 1
查询结果: User(id=1, name=UpdatedName)
关键点
- 跨会话共享缓存:
SqlSession2
未访问数据库。 - 更新操作清空缓存:
SqlSession3
更新后,SqlSession4
重新查询数据库。
2、配置二级缓存(关键步骤)
1.实体类实现 Serializable
:
public class User implements Serializable { // ... }
2.Mapper XML 中添加 <cache/>
:
<mapper namespace="com.example.UserMapper"> <cache eviction="LRU" flushInterval="60000" size="1024"/> </mapper>
3.3. 缓存区别
4、Sql注入
4.1.#{}参数占位符
#{}
是 MyBatis 中的 参数占位符,用于将参数传递到 SQL 语句中。 会将 #{}
中的参数值自动进行转义,将 sql 中的#{}
替换为 ? 号。
作用是将参数值进行处理并填充到 SQL 语句中,在传递参数时会进行预处理,避免 SQL 注入风险。
4.2.${}字符串拼接
${}
是 MyBatis 中的 字符串拼接占位符,用于直接将参数值拼接到 SQL 语句中。
它会将传入的参数值直接替换到 SQL 中的 ${}
所在的位置,而不会进行任何的处理或转义。
由下图所说:
显然#{}直接去数据库查询,是查不到这个username,而${}由于没有进行转义处理,就会执行1=1;
4.3.SQL注入
MyBatis的#{}之所以能够预防SQL注入是因为底层使用了PreparedStatement类的setString()方法来设置参数,此方法会获取传递进来的参数的每个字符,然后进行循环对比,如果发现有敏感字符(如:单引号、双引号等),则会在前面加上一个'/'代表转义此符号,让其变为一个普通的字符串,不参与SQL语句的生成,达到防止SQL注入的效果。
其次${}本身设计的初衷就是为了参与SQL语句的语法生成,自然而然会导致SQL注入的问题(不会考虑字符过滤问题)。
setString方法为什么可以解决。
public void setString(int parameterIndex, String x) throws SQLException { synchronized(this.checkClosed().getConnectionMutex()) { if (x == null) { this.setNull(parameterIndex, 1); } else { this.checkClosed(); int stringLength = x.length(); StringBuilder buf; if (this.connection.isNoBackslashEscapesSet()) { boolean needsHexEscape = this.isEscapeNeededForString(x, stringLength); Object parameterAsBytes; byte[] parameterAsBytes; if (!needsHexEscape) { parameterAsBytes = null; buf = new StringBuilder(x.length() + 2); buf.append('\''); buf.append(x); buf.append('\''); if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(buf.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } else { parameterAsBytes = StringUtils.getBytes(buf.toString()); } this.setInternal(parameterIndex, parameterAsBytes); } else { parameterAsBytes = null; if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } else { parameterAsBytes = StringUtils.getBytes(x); } this.setBytes(parameterIndex, parameterAsBytes); } return; } String parameterAsString = x; boolean needsQuoted = true; if (this.isLoadDataQuery || this.isEscapeNeededForString(x, stringLength)) { needsQuoted = false; buf = new StringBuilder((int)((double)x.length() * 1.1D)); buf.append('\''); for(int i = 0; i < stringLength; ++i) { //遍历字符串,获取到每个字符 char c = x.charAt(i); switch(c) { case '\u0000': buf.append('\\'); buf.append('0'); break; case '\n': buf.append('\\'); buf.append('n'); break; case '\r': buf.append('\\'); buf.append('r'); break; case '\u001a': buf.append('\\'); buf.append('Z'); break; case '"': if (this.usingAnsiMode) { buf.append('\\'); } buf.append('"'); break; case '\'': buf.append('\\'); buf.append('\''); break; case '\\': buf.append('\\'); buf.append('\\'); break; case '': case '₩': if (this.charsetEncoder != null) { CharBuffer cbuf = CharBuffer.allocate(1); ByteBuffer bbuf = ByteBuffer.allocate(1); cbuf.put(c); cbuf.position(0); this.charsetEncoder.encode(cbuf, bbuf, true); if (bbuf.get(0) == 92) { buf.append('\\'); } } buf.append(c); break; default: buf.append(c); } } buf.append('\''); parameterAsString = buf.toString(); } buf = null; byte[] parameterAsBytes; if (!this.isLoadDataQuery) { if (needsQuoted) { parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } else { parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } } else { parameterAsBytes = StringUtils.getBytes(parameterAsString); } this.setInternal(parameterIndex, parameterAsBytes); this.parameterTypes[parameterIndex - 1 + this.getParameterIndexOffset()] = 12; } } }
4.4.用法总结
1)#{}在使用时,会根据传递进来的值来选择是否加上双引号,因此我们传递参数的时候一般都是直接传递,不用加双引号,${}则不会,我们需要手动加。
2)在传递一个参数时,我们说了#{}中可以写任意的值,${}则必须使用value;即:${value}
3)#{}针对SQL注入进行了字符过滤,${}则只是作为普通传值,并没有考虑到这些问题
4)#{}的应用场景是为给SQL语句的where字句传递条件值,${}的应用场景是为了传递一些需要参与SQL语句语法生成的值。
5、懒加载
MyBatis 的懒加载功能允许在需要时才加载关联的对象,从而提高系统的性能。懒加载是一种常用的策略,尤其适用于处理大型数据集时,避免在查询时加载不必要的数据。
5.1、原理
在 MyBatis 中,懒加载是通过延迟加载关联对象的策略实现的。当你访问一个未初始化的属性时,MyBatis 会自动从数据库加载相关的数据,而不是在首次加载父对象时就将所有的关联数据一起加载。
5.2、开启
全局配置: 你可以在 MyBatis 的全局配置文件 mybatis-config.xml
中设置懒加载功能。
<configuration> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> <!-- 控制是否在访问懒加载对象时立即加载 --> </settings> </configuration>
lazmgIpmyLoadingEnabled
: 设置为true
时启用懒加载。aggressiveLazyLoading
: 如果设置为true
,当你访问一个对象的任何属性时,会立即加载所有懒加载的属性。如果设置为false
,那么只有在访问特定的懒加载属性时才会加载。
5.3、实现
假设我们有两个实体:User
和 Order
,每个用户可以有多个订单。这是一个常见的一对多关系的例子。
1. 数据库表结构
CREATE TABLE users ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL ); CREATE TABLE orders ( order_id INT PRIMARY KEY AUTO_INCREMENT, order_number VARCHAR(50) NOT NULL, user_id INT, FOREIGN KEY (user_id) REFERENCES users(user_id) );
2. POJO 类
User.java
import java.util.List; public class User { private Integer userId; private String username; private List<Order> orders; // 一对多关联 // Getters 和 Setters // ... }
Order.java
public class Order { private Integer orderId; private String orderNumber; // Getters 和 Setters // ... }
3. Mapper 接口
我们创建一个 Mapper 接口来定义查询方法。
package com.example.mapper; import com.example.model.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper public interface UserMapper { @Select("SELECT * FROM users WHERE user_id = #{userId}") User findUserById(Integer userId); }
4. XML 映射文件
为了懒加载订单数据,我们可以在 User
类中定义一个方法来获取订单。如果你不希望在获取用户时立即加载用户的所有订单数据,可以通过以下方式进行设计。
UserMapper.xml
<mapper namespace="com.example.mapper.UserMapper"> <resultMap id="userResultMap" type="User"> <id property="userId" column="user_id"/> <result property="username" column="username"/> <collection property="orders" ofType="Order" select="com.example.mapper.OrderMapper.findOrdersByUserId" fetchType="lazy"/> <!-- 使用懒加载 --> </resultMap> <select id="findUserById" resultMap="userResultMap"> SELECT * FROM users WHERE user_id = #{userId} </select> </mapper>
- 在
UserMapper.xml
中,<collection>
标签表示与Order
的一对多关系的懒加载配置。通过select
属性指定查询订单的 SQL。 fetchType="lazy"
表示此集合数据将延迟加载。
OrderMapper.java
package com.example.mapper; import com.example.model.Order; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper public interface OrderMapper { @Select("SELECT * FROM orders WHERE user_id = #{userId}") List<Order> findOrdersByUserId(Integer userId); }
5. 使用懒加载的情况
在你的服务层中,你可以调用 findUserById
方法。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { @Autowired private UserMapper userMapper; public User getUserWithOrders(Integer userId) { User user = userMapper.findUserById(userId); // Orders 还没有被加载,直到你调用 user.getOrders() return user; } }
6. 控制器层
你可以使用 Spring MVC 来创建一个控制器,处理 HTTP 请求。
UserController.java:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public User getUser(@PathVariable Integer id) { return userService.getUserWithOrders(id); } }
7. 使用示例
当你访问 http://localhost:8080/users/1
,会调用 getUser()
方法。
- 一开始,
findUserById
方法会查询用户信息,但订单还不会被加载。 - 当你访问
user.getOrders()
时,MyBatis 会自动调用OrderMapper.findOrdersByUserId
方法来查询与该用户相关的订单。
8. 总结
以上代码演示了如何在 MyBatis 中实现懒加载功能。通过配置 XML 文件中的 <collection>
标签结合 fetchType="lazy"
设置,可以确保在访问相关集合属性时才加载数据。这能帮助提高性能,减少不必要的数据加载。
懒加载机制在处理大数据量、频繁不使用的关联对象时尤为重要,有效节约系统资源。
6、常用问题
6.1、映射不一致
当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
使用 @Results
注解或者 <resultMap>
来明确映射关系。
假设我们有一个数据库表 users
,其结构如下:
在项目中,有一个对应的 POJO 类 User
,其属性使用驼峰命名风格:
public class User { private Integer id; // 对应 user_id private String username; // 对应 user_name private String email; // 对应 user_email get set方法略 }
1、使用 XML(resultMap)
1. 创建 MyBatis 映射文件
将使用 XML 文件 UserMapper.xml
来定义 SQL 操作和字段映射。这个文件通常位于 resources
目录下。
UserMapper.xml:
<mapper namespace="com.example.mapper.UserMapper"> <!-- 定义结果映射 --> <resultMap id="UserResultMap" type="User"> <result property="id" column="user_id"/> <result property="username" column="user_name"/> <result property="email" column="user_email"/> </resultMap> <!-- 查询用户的 SQL 操作 --> <select id="getUserById" resultMap="UserResultMap"> SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id} </select> </mapper>
2、使用(Results)
如果你想使用注解的方式,可以直接在 Mapper 接口中使用 @Select
和 @Results
注解来实现。下面是一个示例:
UserMapper.java(注解方式):
package com.example.mapper; import com.example.model.User; import org.apache.ibatis.annotations.*; @Mapper public interface UserMapper { @Select("SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}") @Results({ @Result(property = "id", column = "user_id"), @Result(property = "username", column = "user_name"), @Result(property = "email", column = "user_email") }) User getUserById(Integer id); }
3、JsonProperty
@JsonProperty("user_id") // 将 JSON 中的 user_id 映射到 id private Integer id; @JsonProperty("user_name") // 将 JSON 中的 user_name 映射到 username private String username; @JsonProperty("user_email") // 将 JSON 中的 user_email 映射到 email private String email;
import com.fasterxml.jackson.databind.ObjectMapper; public class Main { public static void main(String[] args) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); // 创建一个 User 对象 User user = new User(); user.setId(1); user.setUsername("alice"); user.setEmail("alice@example.com"); // 将 User 对象序列化为 JSON 字符串 String jsonString = objectMapper.writeValueAsString(user); System.out.println("Serialized JSON: " + jsonString); } } 输出: {"user_id":1,"user_name":"alice","user_email":"alice@example.com"}
package com.example.mapper; import com.example.model.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface UserMapper { List<User> findUsersByUsernameLike(@Param("username") String username); }
@JsonProperty
注解帮助在序列化和反序列化过程中,指定 JSON 字段与 POJO 属性之间的映射关系。
6.2、模糊查询
1.CONCAT
创建一个 Mapper 接口,并添加一个模糊查询的方法。
package com.example.mapper; import com.example.model.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface UserMapper { List<User> findUsersByUsernameLike(@Param("username") String username); }
在 Mapper XML 文件中定义模糊查询的 SQL 语句。我们将使用 LIKE
关键字进行模糊匹配。
UserMapper.xml:
<mapper namespace="com.example.mapper.UserMapper"> <select id="findUsersByUsernameLike" resultType="User"> SELECT user_id, username, email FROM users WHERE username LIKE CONCAT('%', #{username}, '%') </select> </mapper>
2.传递参数%value%
在传入参数的时候比如username:%value% 传入到mapper文件里面;
6.3、动态标签
1. <if>
用于判断条件,如果条件满足则会生成 SQL 语句中的代码。
示例:
<select id="findUserByCriteria" resultType="User"> SELECT * FROM users WHERE 1=1 <if test="username != null"> AND username = #{username} </if> <if test="email != null"> AND email = #{email} </if> </select>
在这个例子中,只有当 username
或 email
不为 null
时,相应的条件才会被添加到 SQL 中。
2. <choose>
、<when>
和 <otherwise>
用于多条件选择的情况,类似于 Java 中的 switch-case 语句。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserService { @Autowired private UserMapper userMapper; public List<User> findUsers(String status) { return userMapper.findUsersByStatus(status); } }
<select id="findUserByStatus" resultType="User"> SELECT * FROM users WHERE <choose> <when test="status == 'active'"> status = 'active' </when> <when test="status == 'inactive'"> status = 'inactive' </when> <otherwise> status IS NOT NULL </otherwise> </choose> </select>
在这个例子中,SQL 查询将会根据 status
的值选择生成查询条件。
3.<foreach>
用于循环遍历集合,通常用于生成 IN 列表或多条 SQL 语句。
示例:
<select id="findUsersByIds" resultType="User"> SELECT * FROM users WHERE user_id IN <foreach item="id" collection="userIds" open="(" separator="," close=")"> #{id} </foreach> </select>
4. <trim>
用于去掉 SQL 语句前后可能出现的多余的字符,主要是在构建可选条件时使用,查询参数可以是可选的。
<select id="findUsersByCondition" resultType="User"> SELECT android* 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>
<trim>
可以去掉多余的前缀,比如如果没有设定 username
和 email
,可以避免生成 WHERE
后面紧跟着的 AND
。
总结
这篇关于关于Mybatis和JDBC的使用及区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!