}”, “?”);
// 执行sql
PreparedStatement ps = null;
ResultSet rs = null;
Object obj = null;
try {
ps = connection.prepareStatement(sql);
ps.setString(1, parameterObj.toString());
rs = ps.executeQuery();
if (rs.next()) {
// 将结果集封装对象,通过反射
String resultType = godMappedStatement.getResultType();
Class<?> aClass = Class.forName(resultType);
Constructor<?> con = aClass.getDeclaredConstructor();
obj = con.newInstance();
// 给对象obj属性赋值
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String columnName = rsmd.getColumnName(i);
String setMethodName = “set” + columnName.toUpperCase().charAt(0) + columnName.substring(1);
Method setMethod = aClass.getDeclaredMethod(setMethodName, aClass.getDeclaredField(columnName).getType());
setMethod.invoke(obj, rs.getString(columnName));
}
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
try {
ps.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return obj;
}
<a name="mWyJ3"></a>
## 5.3 GodBatis使用Maven打包
![23B91E36-8138-40bd-960F-685051751FBE.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660206374772-d21754cb-0c5f-4a14-921e-d083b4a791c3.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_14%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%233b4043&clientId=u77faa741-d191-4&from=paste&height=424&id=ud190761b&originHeight=424&originWidth=489&originalType=binary&ratio=1&rotation=0&showTitle=false&size=11737&status=done&style=none&taskId=u7b711654-0ab3-40b0-9395-9f98639e6dd&title=&width=489)<br />查看本地仓库中是否已经有jar包:<br />![089FD2B9-D3F2-4f62-9A64-2EC66CC8B623.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660206446577-20e8dd84-6b05-48c8-a693-cb0dadfb7979.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_14%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23fbf5f5&clientId=u77faa741-d191-4&from=paste&height=232&id=u0ec7ef39&originHeight=232&originWidth=497&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8571&status=done&style=none&taskId=ub79cbd13-af08-4c00-a5be-feee1bd1563&title=&width=497)
<a name="g2yqB"></a>
## 5.4 使用GodBatis
使用GodBatis就和使用MyBatis是一样的。<br />第一步:准备数据库表t_user<br />![96BB0AE2-0C47-4e24-AF86-E8B875B5B65A.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660206701289-4ed4661b-7c85-4157-b09c-3a7fe5207399.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_23%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23f7f6f6&clientId=u77faa741-d191-4&from=paste&height=264&id=uef3387dc&originHeight=264&originWidth=796&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12816&status=done&style=none&taskId=ufd123940-ed51-40c8-a680-18ad7e51c77&title=&width=796)<br />第二步:创建模块,普通的Java Maven模块:godbatis-test<br />第三步:引入依赖
xml
<?xml version=”1.0” encoding=”UTF-8”?>
4.0.0
com.powernode
godbatis-test
1.0-SNAPSHOT
jar
org.god
godbatis
1.0.0
mysql
mysql-connector-java
8.0.30
junit
junit
4.13.2
test
17
17
第四步:编写pojo类
java
package com.powernode.godbatis.pojo;
public class User {
private String id;
private String name;
private String email;
private String address;
@Override
public String toString() {
return “User{“ +
“id=’” + id + ‘\’’ +
“, name=’” + name + ‘\’’ +
“, email=’” + email + ‘\’’ +
“, address=’” + address + ‘\’’ +
‘}’;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public User() {
}
public User(String id, String name, String email, String address) {
this.id = id;
this.name = name;
this.email = email;
this.address = address;
}
}
第五步:编写核心配置文件:godbatis-config.xml
xml
<?xml version=”1.0” encoding=”UTF-8” ?>
第六步:编写sql映射文件:UserMapper.xml
xml
<?xml version=”1.0” encoding=”UTF-8” ?>
insert into t_user(id,name,email,address) values(#{id},#{name},#{email},#{address})
第七步:编写测试类
java
package com.powernode.godbatis.test;
import com.powernode.godbatis.pojo.User;
import org.god.core.Resources;
import org.god.core.SqlSession;
import org.god.core.SqlSessionFactory;
import org.god.core.SqlSessionFactoryBuilder;
import org.junit.Test;
public class GodBatisTest {
@Test
public void testInsertUser() throws Exception{
User user = new User(“1”, “zhangsan”, “zhangsan@1234.com”, “北京大兴区”);
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourcesAsStream(“godbatis-config.xml”));
SqlSession sqlSession = sqlSessionFactory.openSession();
int count = sqlSession.insert(“user.insertUser”, user);
System.out.println(“插入了几条记录:” + count);
sqlSession.commit();
sqlSession.close();
}
@Test
public void testSelectUserById() throws Exception{
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourcesAsStream(“godbatis-config.xml”));
SqlSession sqlSession = sqlSessionFactory.openSession();
Object user = sqlSession.selectOne(“user.selectUserById”, “1”);
System.out.println(user);
sqlSession.close();
}
}
第八步:运行结果<br />![4766EF58-0505-4cdb-8539-D3E487D0B40D.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660209177038-ecf701a2-1211-4f0c-b1f6-e2e16c1617d5.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_13%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23333232&clientId=u77faa741-d191-4&from=paste&height=135&id=u32d04b9b&originHeight=135&originWidth=447&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7682&status=done&style=none&taskId=u71f55f3d-42b0-4389-bf0f-179229f7aa2&title=&width=447)<br />![DE9D2710-1999-4ef1-8F7E-822ACB17BF02.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660209186576-54493410-47b7-4496-8007-471473cc6fc6.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_13%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23f2f1ef&clientId=u77faa741-d191-4&from=paste&height=134&id=u1f31d223&originHeight=134&originWidth=443&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6439&status=done&style=none&taskId=udddf5f59-e9ac-437d-873d-d900c3aa5b4&title=&width=443)<br />![CD0A068F-8A77-4ced-B92F-B0CCA2412B3C.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660209193124-908993c5-90d6-4426-a598-7aeac2a6b791.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_23%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%238b7760&clientId=u77faa741-d191-4&from=paste&height=195&id=uee2c9fb1&originHeight=195&originWidth=819&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12996&status=done&style=none&taskId=u35b9af92-a008-4883-88b5-605a8697d9b&title=&width=819)
<a name="dhqaX"></a>
## 5.5 总结MyBatis框架的重要实现原理
xml
<?xml version=”1.0” encoding=”UTF-8” ?>
insert into t_user(id,name,email,address) values(#{id},#{name},#{email},#{address})
思考两个问题:
- 为什么insert语句中 #{} 里填写的必须是属性名?
- 为什么select语句查询结果列名要属性名一致?
![](https://cdn.nlark.com/yuque/0/2022/png/21376908/1659578619308-ceb8077a-94a7-4f64-b41d-e54b3c14e7fb.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_34%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23d9d7d7&from=url&id=JHMnK&originHeight=152&originWidth=1180&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="Uh93K"></a>
# 六、在WEB中应用MyBatis(使用MVC架构模式)
**目标:**
- 掌握mybatis在web应用中怎么用
- mybatis三大对象的作用域和生命周期
- ThreadLocal原理及使用
- 巩固MVC架构模式
- 为学习MyBatis的接口代理机制做准备
**实现功能:**
- 银行账户转账
**使用技术:**
- HTML + Servlet + MyBatis
**WEB应用的名称:**
- bank
<a name="X2rtg"></a>
## 6.1 需求描述
![9C2CCE44-E51B-4b1a-AD3D-176294F3B463.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660274775552-da896b17-09dd-455a-899e-eb4f36fc0ced.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_14%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23faf9f8&clientId=u01870008-33ee-4&from=paste&height=213&id=ud5c2edc9&originHeight=213&originWidth=497&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7952&status=done&style=none&taskId=u2c5645cb-8235-46ee-9ba7-b078802b235&title=&width=497)
<a name="iycZI"></a>
## 6.2 数据库表的设计和准备数据
![68216368-5BD6-4e0d-A567-5E725A349D0F.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660275027117-dcf9ec03-01fa-4e93-8edb-7e10456ba51f.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_26%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23f6f5f5&clientId=u01870008-33ee-4&from=paste&height=235&id=u823c564f&originHeight=235&originWidth=920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=13719&status=done&style=none&taskId=ua26ee15c-9771-4acb-bef3-aa72e8aa600&title=&width=920)<br />![EBB28A3F-32BB-431c-9C2C-03A4EB5C8ADF.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660275097707-2621f88d-9c21-4d4e-aa6c-c90e1c6e4e3b.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_14%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23f5f4f3&clientId=u01870008-33ee-4&from=paste&height=153&id=u58e9f113&originHeight=153&originWidth=506&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7100&status=done&style=none&taskId=ue7979c28-a5e7-4ae3-a693-32d1bcf8818&title=&width=506)
<a name="sk9Zu"></a>
## 6.3 实现步骤
<a name="pwaXR"></a>
### 第一步:环境搭建
- IDEA中创建Maven WEB应用(**mybatis-004-web**)
![B228544B-4646-41fc-BFA1-39B6E8464405.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660275706327-2116fb91-fe1a-449d-bd62-6f15416dba84.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_32%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%233d4043&clientId=u01870008-33ee-4&from=paste&height=872&id=ub0eb7faa&originHeight=872&originWidth=1133&originalType=binary&ratio=1&rotation=0&showTitle=false&size=39856&status=done&style=none&taskId=u161a6991-d0d4-4def-b73e-0f0a48d6a9f&title=&width=1133)
- IDEA配置Tomcat,这里Tomcat使用10+版本。并部署应用到tomcat。
![38908B1E-AC59-4ecf-B972-7651AA58023E.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660296669590-e7e4932f-cdfb-4d82-9711-af0ca18bbe81.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_30%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%233d4043&clientId=ueb159dc3-e063-4&from=paste&height=802&id=ufcc208d1&originHeight=802&originWidth=1042&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35630&status=done&style=none&taskId=uaf0f65ec-da3c-4e15-a203-6397277702e&title=&width=1042)<br />![9EA5CF36-E282-41d4-B544-1533AD25BB33.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660296712190-729ead72-375d-417f-852a-ef366c38c1c3.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_30%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%233b3f42&clientId=ueb159dc3-e063-4&from=paste&height=807&id=uf0e62e86&originHeight=807&originWidth=1049&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23858&status=done&style=none&taskId=uf5f95120-d65b-4b10-9dcf-10db2430be9&title=&width=1049)
- 默认创建的maven web应用没有java和resources目录,包括两种解决方案
- 第一种:自己手动加上。
![5A0EC4A7-C5EB-4fcb-B74C-2A9B3A147E5B.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660297753549-b90c4f4c-2f8f-404d-9ff7-c350d8cd21e0.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_11%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%2390aeaa&clientId=ueb159dc3-e063-4&from=paste&height=448&id=u9499e54b&originHeight=448&originWidth=378&originalType=binary&ratio=1&rotation=0&showTitle=false&size=13734&status=done&style=none&taskId=uba85100e-6099-41c8-909c-b9b59c5efa9&title=&width=378)
- 第二种:修改maven-archetype-webapp-1.4.jar中的配置文件
![508D0561-6CEF-4af4-A243-0CF29B2D748B.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660297555336-bef8649f-7e24-477c-9e0a-e55ed1b009a6.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_22%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23fbf9f8&clientId=ueb159dc3-e063-4&from=paste&height=303&id=uc2301bdd&originHeight=303&originWidth=776&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16245&status=done&style=none&taskId=u6e49a14f-1a0e-4c01-97db-b8750ee5ccf&title=&width=776)<br />![3A2D1F51-98DE-4416-B196-2535C5D26E55.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660297620748-1062587b-feff-472a-a546-e5489aebdbdc.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_21%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23c2deb4&clientId=ueb159dc3-e063-4&from=paste&height=262&id=ucbfa4d97&originHeight=262&originWidth=751&originalType=binary&ratio=1&rotation=0&showTitle=false&size=19674&status=done&style=none&taskId=udd20f8b4-d71b-417f-810a-ce5e9e75ca7&title=&width=751)<br />![7F5D0F8B-EA3B-4d79-B156-02C0D11BA2E9.png](https://cdn.nlark.com/yuque/0/2022/png/21376908/1660297684026-b50cdd30-80d2-488d-9632-a0a098a3518e.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_23%2Ctext_5Yqo5Yqb6IqC54K5%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23fdfaf9&clientId=ueb159dc3-e063-4&from=paste&height=405&id=ua8158036&originHeight=405&originWidth=814&originalType=binary&ratio=1&rotation=0&showTitle=false&size=14535&status=done&style=none&taskId=u735b56bd-0a3a-44c9-b7b5-80dbdab0d78&title=&width=814)
- web.xml文件的版本较低,可以从tomcat10的样例文件中复制,然后修改
```xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0"
metadata-complete="true">
</web-app>
- 删除index.jsp文件,因为我们这个项目不使用JSP。只使用html。
- 确定pom.xml文件中的打包方式是war包。
- 引入相关依赖
- 编译器版本修改为17
- 引入的依赖包括:mybatis,mysql驱动,junit,logback,servlet。
```xml
<?xml version=”1.0” encoding=”UTF-8”?>
4.0.0
com.powernode
mybatis-004-web
1.0-SNAPSHOT
war
mybatis-004-web
http://localhost:8080/bank
UTF-8
17
17
org.mybatis
mybatis
3.5.10
mysql
mysql-connector-java
8.0.30
junit
junit
4.13.2
test
ch.qos.logback
logback-classic
1.2.11
jakarta.servlet
jakarta.servlet-api
5.0.0
provided
mybatis-004-web
maven-clean-plugin
3.1.0
maven-resources-plugin
3.0.2
maven-compiler-plugin
3.8.0
maven-surefire-plugin
2.22.1
maven-war-plugin
3.2.2
maven-install-plugin
2.5.2
maven-deploy-plugin
2.8.2
- 引入相关配置文件,放到resources目录下(全部放到类的根路径下)
- mybatis-config.xml
- AccountMapper.xml
- logback.xml
- jdbc.properties
```properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode
jdbc.username=root
jdbc.password=root
<?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="jdbc.properties"/>
<environments default="dev">
<environment id="dev">
<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 resource="AccountMapper.xml"/>
</mappers>
</configuration>
<?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="account">
</mapper>
第二步:前端页面index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>银行账户转账</title>
</head>
<body>
<!--/bank是应用的根,部署web应用到tomcat的时候一定要注意这个名字-->
<form action="/bank/transfer" method="post">
转出账户:<input type="text" name="fromActno"/><br>
转入账户:<input type="text" name="toActno"/><br>
转账金额:<input type="text" name="money"/><br>
<input type="submit" value="转账"/>
</form>
</body>
</html>
第三步:创建pojo包、service包、dao包、web包、utils包
- com.powernode.bank.pojo
- com.powernode.bank.service
- com.powernode.bank.service.impl
- com.powernode.bank.dao
- com.powernode.bank.dao.impl
- com.powernode.bank.web.controller
- com.powernode.bank.exception
- com.powernode.bank.utils:将之前编写的SqlSessionUtil工具类拷贝到该包下。
第四步:定义pojo类:Account
```java
package com.powernode.bank.pojo;
/**
- 银行账户类
- @author 老杜
- @version 1.0
@since 1.0
*/
public class Account {
private Long id;
private String actno;
private Double balance;
@Override
public String toString() {
return "Account{" +
"id=" + id +
", actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
public Account() {
}
public Account(Long id, String actno, Double balance) {
this.id = id;
this.actno = actno;
this.balance = balance;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
}
<a name="BHJnB"></a>
### 第五步:编写AccountDao接口,以及AccountDaoImpl实现类
分析dao中至少要提供几个方法,才能完成转账:
- 转账前需要查询余额是否充足:selectByActno
- 转账时要更新账户:update
```java
package com.powernode.bank.dao;
import com.powernode.bank.pojo.Account;
/**
* 账户数据访问对象
* @author 老杜
* @version 1.0
* @since 1.0
*/
public interface AccountDao {
/**
* 根据账号获取账户信息
* @param actno 账号
* @return 账户信息
*/
Account selectByActno(String actno);
/**
* 更新账户信息
* @param act 账户信息
* @return 1表示更新成功,其他值表示失败
*/
int update(Account act);
}
package com.powernode.bank.dao.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtil.openSession();
Account act = (Account)sqlSession.selectOne("selectByActno", actno);
sqlSession.close();
return act;
}
@Override
public int update(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("update", act);
sqlSession.commit();
sqlSession.close();
return count;
}
}
第六步:AccountDaoImpl中编写了mybatis代码,需要编写SQL映射文件了
<?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="account">
<select id="selectByActno" resultType="com.powernode.bank.pojo.Account">
select * from t_act where actno = #{actno}
</select>
<update id="update">
update t_act set balance = #{balance} where actno = #{actno}
</update>
</mapper>
第七步:编写AccountService接口以及AccountServiceImpl
package com.powernode.bank.exception;
/**
* 余额不足异常
* @author 老杜
* @version 1.0
* @since 1.0
*/
public class MoneyNotEnoughException extends Exception{
public MoneyNotEnoughException(){}
public MoneyNotEnoughException(String msg){ super(msg); }
}
package com.powernode.bank.exception;
/**
* 应用异常
* @author 老杜
* @version 1.0
* @since 1.0
*/
public class AppException extends Exception{
public AppException(){}
public AppException(String msg){ super(msg); }
}
package com.powernode.bank.service;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
/**
* 账户业务类。
* @author 老杜
* @version 1.0
* @since 1.0
*/
public interface AccountService {
/**
* 银行账户转正
* @param fromActno 转出账户
* @param toActno 转入账户
* @param money 转账金额
* @throws MoneyNotEnoughException 余额不足异常
* @throws AppException App发生异常
*/
void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException;
}
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {
// 查询转出账户的余额
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new MoneyNotEnoughException("对不起,您的余额不足。");
}
try {
// 程序如果执行到这里说明余额充足
// 修改账户余额
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
// 更新数据库
accountDao.update(fromAct);
accountDao.update(toAct);
} catch (Exception e) {
throw new AppException("转账失败,未知原因!");
}
}
}
第八步:编写AccountController
package com.powernode.bank.web.controller;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.service.impl.AccountServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 账户控制器
* @author 老杜
* @version 1.0
* @since 1.0
*/
@WebServlet("/transfer")
public class AccountController extends HttpServlet {
private AccountService accountService = new AccountServiceImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取响应流
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 获取账户信息
String fromActno = request.getParameter("fromActno");
String toActno = request.getParameter("toActno");
double money = Integer.parseInt(request.getParameter("money"));
// 调用业务方法完成转账
try {
accountService.transfer(fromActno, toActno, money);
out.print("<h1>转账成功!!!</h1>");
} catch (MoneyNotEnoughException e) {
out.print(e.getMessage());
} catch (AppException e) {
out.print(e.getMessage());
}
}
}
启动服务器,打开浏览器,输入地址:http://localhost:8080/bank,测试:
6.4 MyBatis对象作用域以及事务问题
MyBatis核心对象的作用域
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
事务问题
在之前的转账业务中,更新了两个账户,我们需要保证它们的同时成功或同时失败,这个时候就需要使用事务机制,在transfer方法开始执行时开启事务,直到两个更新都成功之后,再提交事务,我们尝试将transfer方法进行如下修改:
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {
// 查询转出账户的余额
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new MoneyNotEnoughException("对不起,您的余额不足。");
}
try {
// 程序如果执行到这里说明余额充足
// 修改账户余额
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
// 更新数据库(添加事务)
SqlSession sqlSession = SqlSessionUtil.openSession();
accountDao.update(fromAct);
// 模拟异常
String s = null;
s.toString();
accountDao.update(toAct);
sqlSession.commit();
sqlSession.close();
} catch (Exception e) {
throw new AppException("转账失败,未知原因!");
}
}
}
运行前注意看数据库表中当前的数据:
执行程序:
再次查看数据库表中的数据:
傻眼了吧!!!事务出问题了,转账失败了,钱仍然是少了1万。这是什么原因呢?主要是因为service和dao中使用的SqlSession对象不是同一个。
怎么办?为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到ThreadLocal当中。修改SqlSessionUtil工具类:
package com.powernode.bank.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
/**
* MyBatis工具类
*
* @author 老杜
* @version 1.0
* @since 1.0
*/
public class SqlSessionUtil {
private static SqlSessionFactory sqlSessionFactory;
/**
* 类加载时初始化sqlSessionFactory对象
*/
static {
try {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (Exception e) {
e.printStackTrace();
}
}
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
/**
* 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。
*
* @return 新的会话对象
*/
public static SqlSession openSession() {
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
local.set(sqlSession);
}
return sqlSession;
}
/**
* 关闭SqlSession对象
* @param sqlSession
*/
public static void close(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.close();
}
local.remove();
}
}
修改dao中的方法:AccountDaoImpl中所有方法中的提交commit和关闭close代码全部删除。
package com.powernode.bank.dao.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtil.openSession();
Account act = (Account)sqlSession.selectOne("account.selectByActno", actno);
return act;
}
@Override
public int update(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("account.update", act);
return count;
}
}
修改service中的方法:
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {
// 查询转出账户的余额
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new MoneyNotEnoughException("对不起,您的余额不足。");
}
try {
// 程序如果执行到这里说明余额充足
// 修改账户余额
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
// 更新数据库(添加事务)
SqlSession sqlSession = SqlSessionUtil.openSession();
accountDao.update(fromAct);
// 模拟异常
String s = null;
s.toString();
accountDao.update(toAct);
sqlSession.commit();
SqlSessionUtil.close(sqlSession); // 只修改了这一行代码。
} catch (Exception e) {
throw new AppException("转账失败,未知原因!");
}
}
}
当前数据库表中的数据:
再次运行程序:
查看数据库表:没有问题。
再测试转账成功:
如果余额不足呢:
账户的余额依然正常:
6.5 分析当前程序存在的问题
我们来看一下DaoImpl的代码
package com.powernode.bank.dao.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtil.openSession();
Account act = (Account)sqlSession.selectOne("account.selectByActno", actno);
return act;
}
@Override
public int update(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("account.update", act);
return count;
}
}
我们不难发现,这个dao实现类中的方法代码很固定,基本上就是一行代码,通过SqlSession对象调用insert、delete、update、select等方法,这个类中的方法没有任何业务逻辑,既然是这样,这个类我们能不能动态的生成,以后可以不写这个类吗?答案:可以。
七、使用javassist生成类
来自百度百科:
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态”AOP”框架。
7.1 Javassist的使用
我们要使用javassist,首先要引入它的依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.1-GA</version>
</dependency>
样例代码:
package com.powernode.javassist;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import java.lang.reflect.Method;
public class JavassistTest {
public static void main(String[] args) throws Exception {
// 获取类池
ClassPool pool = ClassPool.getDefault();
// 创建类
CtClass ctClass = pool.makeClass("com.powernode.javassist.Test");
// 创建方法
// 1.返回值类型 2.方法名 3.形式参数列表 4.所属类
CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, ctClass);
// 设置方法的修饰符列表
ctMethod.setModifiers(Modifier.PUBLIC);
// 设置方法体
ctMethod.setBody("{System.out.println(\"hello world\");}");
// 给类添加方法
ctClass.addMethod(ctMethod);
// 调用方法
Class<?> aClass = ctClass.toClass();
Object o = aClass.newInstance();
Method method = aClass.getDeclaredMethod("execute");
method.invoke(o);
}
}
运行要注意:加两个参数,要不然会有异常。
- —add-opens java.base/java.lang=ALL-UNNAMED
- —add-opens java.base/sun.net.util=ALL-UNNAMED
运行结果:
7.2 使用Javassist生成DaoImpl类
使用Javassist动态生成DaoImpl类
package com.powernode.bank.utils;
import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* 使用javassist库动态生成dao接口的实现类
*
* @author 老杜
* @version 1.0
* @since 1.0
*/
public class GenerateDaoByJavassist {
/**
* 根据dao接口生成dao接口的代理对象
*
* @param sqlSession sql会话
* @param daoInterface dao接口
* @return dao接口代理对象
*/
public static Object getMapper(SqlSession sqlSession, Class daoInterface) {
ClassPool pool = ClassPool.getDefault();
// 生成代理类
CtClass ctClass = pool.makeClass(daoInterface.getPackageName() + ".impl." + daoInterface.getSimpleName() + "Impl");
// 接口
CtClass ctInterface = pool.makeClass(daoInterface.getName());
// 代理类实现接口
ctClass.addInterface(ctInterface);
// 获取所有的方法
Method[] methods = daoInterface.getDeclaredMethods();
Arrays.stream(methods).forEach(method -> {
// 拼接方法的签名
StringBuilder methodStr = new StringBuilder();
String returnTypeName = method.getReturnType().getName();
methodStr.append(returnTypeName);
methodStr.append(" ");
String methodName = method.getName();
methodStr.append(methodName);
methodStr.append("(");
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
methodStr.append(parameterTypes[i].getName());
methodStr.append(" arg");
methodStr.append(i);
if (i != parameterTypes.length - 1) {
methodStr.append(",");
}
}
methodStr.append("){");
// 方法体当中的代码怎么写?
// 获取sqlId(这里非常重要:因为这行代码导致以后namespace必须是接口的全限定接口名,sqlId必须是接口中方法的方法名。)
String sqlId = daoInterface.getName() + "." + methodName;
// 获取SqlCommondType
String sqlCommondTypeName = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();
if ("SELECT".equals(sqlCommondTypeName)) {
methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
methodStr.append("Object obj = sqlSession.selectOne(\"" + sqlId + "\", arg0);");
methodStr.append("return (" + returnTypeName + ")obj;");
} else if ("UPDATE".equals(sqlCommondTypeName)) {
methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
methodStr.append("int count = sqlSession.update(\"" + sqlId + "\", arg0);");
methodStr.append("return count;");
}
methodStr.append("}");
System.out.println(methodStr);
try {
// 创建CtMethod对象
CtMethod ctMethod = CtMethod.make(methodStr.toString(), ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
// 将方法添加到类
ctClass.addMethod(ctMethod);
} catch (CannotCompileException e) {
throw new RuntimeException(e);
}
});
try {
// 创建代理对象
Class<?> aClass = ctClass.toClass();
Constructor<?> defaultCon = aClass.getDeclaredConstructor();
Object o = defaultCon.newInstance();
return o;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
修改AccountMapper.xml文件:namespace必须是dao接口的全限定名称,id必须是dao接口中的方法名:
<?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.powernode.bank.dao.AccountDao">
<select id="selectByActno" resultType="com.powernode.bank.pojo.Account">
select * from t_act where actno = #{actno}
</select>
<update id="update">
update t_act set balance = #{balance} where actno = #{actno}
</update>
</mapper>
修改service类中获取dao对象的代码:
启动服务器:启动过程中显示,tomcat服务器自动添加了以下的两个运行参数。所以不需要再单独配置。
测试前数据:
打开浏览器测试:
八、MyBatis中接口代理机制及使用
好消息!!!其实以上所讲内容mybatis内部已经实现了。直接调用以下代码即可获取dao接口的代理类:
AccountDao accountDao = (AccountDao)sqlSession.getMapper(AccountDao.class);
使用以上代码的前提是:AccountMapper.xml文件中的namespace必须和dao接口的全限定名称一致,id必须和dao接口中方法名一致。
将service中获取dao对象的代码再次修改,如下:
测试前数据:
测试后数据:
九、MyBatis小技巧
9.1 #{}和${}
{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。
${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到。
需求:根据car_type查询汽车
模块名:mybatis-005-antic
使用#{}
依赖
<?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.powernode</groupId>
<artifactId>mybatis-005-antic</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!--mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--logback依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
jdbc.properties放在类的根路径下
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode
jdbc.username=root
jdbc.password=root
logback.xml,可以拷贝之前的,放到类的根路径下
utils
package com.powernode.mybatis.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
/**
* MyBatis工具类
*
* @author 老杜
* @version 1.0
* @since 1.0
*/
public class SqlSessionUtil {
private static SqlSessionFactory sqlSessionFactory;
/**
* 类加载时初始化sqlSessionFactory对象
*/
static {
try {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (Exception e) {
e.printStackTrace();
}
}
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
/**
* 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。
*
* @return 新的会话对象
*/
public static SqlSession openSession() {
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
local.set(sqlSession);
}
return sqlSession;
}
/**
* 关闭SqlSession对象
* @param sqlSession
*/
public static void close(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.close();
}
local.remove();
}
}
pojo
package com.powernode.mybatis.pojo;
/**
* 普通实体类:汽车
* @author 老杜
* @version 1.0
* @since 1.0
*/
public class Car {
private Long id;
private String carNum;
private String brand;
private Double guidePrice;
private String produceTime;
private String carType;
// 构造方法
// set get方法
// toString方法
}
mapper接口
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import java.util.List;
/**
* Car的sql映射对象
* @author 老杜
* @version 1.0
* @since 1.0
*/
public interface CarMapper {
/**
* 根据car_num获取Car
* @param carType
* @return
*/
List<Car> selectByCarType(String carType);
}
mybatis-config.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="jdbc.properties"/>
<environments default="dev">
<environment id="dev">
<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 resource="CarMapper.xml"/>
</mappers>
</configuration>
CarMapper.xml,放在类的根路径下:注意namespace必须和接口名一致。id必须和接口中方法名一致。
<?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.powernode.mybatis.mapper.CarMapper">
<select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
car_type = #{carType}
</select>
</mapper>
测试程序
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
import java.util.List;
/**
* CarMapper测试类
* @author 老杜
* @version 1.0
* @since 1.0
*/
public class CarMapperTest {
@Test
public void testSelectByCarType(){
CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectByCarType("燃油车");
cars.forEach(car -> System.out.println(car));
}
}
执行结果:
通过执行可以清楚的看到,sql语句中是带有 ? 的,这个 ? 就是大家在JDBC中所学的占位符,专门用来接收值的。
把“燃油车”以String类型的值,传递给 ?
这就是 #{},它会先进行sql语句的预编译,然后再给占位符传值
使用${}
同样的需求,我们使用${}来完成
CarMapper.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.powernode.mybatis.mapper.CarMapper">
<select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
<!--car_type = #{carType}-->
car_type = ${carType}
</select>
</mapper>
再次运行测试程序:
出现异常了,这是为什么呢?看看生成的sql语句:
很显然,${} 是先进行sql语句的拼接,然后再编译,出现语法错误是正常的,因为 燃油车 是一个字符串,在sql语句中应该添加单引号
修改:
<?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.powernode.mybatis.mapper.CarMapper">
<select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
<!--car_type = #{carType}-->
<!--car_type = ${carType}-->
car_type = '${carType}'
</select>
</mapper>
再执行测试程序:
通过以上测试,可以看出,对于以上这种需求来说,还是建议使用 #{} 的方式。
原则:能用 #{} 就不用 ${}
什么情况下必须使用${}
当需要进行sql语句关键字拼接的时候。必须使用${}
需求:通过向sql语句中注入asc或desc关键字,来完成数据的升序或降序排列。
CarMapper接口:
/**
* 查询所有的Car
* @param ascOrDesc asc或desc
* @return
*/
List<Car> selectAll(String ascOrDesc);
CarMapper.xml文件:
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
order by carNum #{key}
</select>
测试程序
@Test
public void testSelectAll(){
CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectAll("desc");
cars.forEach(car -> System.out.println(car));
}
运行:
报错的原因是sql语句不合法,因为采用这种方式传值,最终sql语句会是这样:
select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car order by carNum ‘desc’
desc是一个关键字,不能带单引号的,所以在进行sql语句关键字拼接的时候,必须使用${}
使用${} 改造
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
<!--order by carNum #{key}-->
order by carNum ${key}
</select>
再次执行测试程序:
拼接表名
业务背景:实际开发中,有的表数据量非常庞大,可能会采用分表方式进行存储,比如每天生成一张表,表的名字与日期挂钩,例如:2022年8月1日生成的表:t_user20220108。2000年1月1日生成的表:t_user20000101。此时前端在进行查询的时候会提交一个具体的日期,比如前端提交的日期为:2000年1月1日,那么后端就会根据这个日期动态拼接表名为:t_user20000101。有了这个表名之后,将表名拼接到sql语句当中,返回查询结果。那么大家思考一下,拼接表名到sql语句当中应该使用#{} 还是 ${} 呢?
使用#{}会是这样:select from ‘t_car’
使用${}会是这样:select from t_car
<select id="selectAllByTableName" resultType="car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
${tableName}
</select>
/**
* 根据表名查询所有的Car
* @param tableName
* @return
*/
List<Car> selectAllByTableName(String tableName);
@Test
public void testSelectAllByTableName(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectAllByTableName("t_car");
cars.forEach(car -> System.out.println(car));
}
执行结果:
批量删除
业务背景:一次删除多条记录。
对应的sql语句:
delete from t_user where id = 1 or id = 2 or id = 3;
- delete from t_user where id in(1, 2, 3);
假设现在使用in的方式处理,前端传过来的字符串:1, 2, 3
如果使用mybatis处理,应该使用#{} 还是 ${}
使用#{} :delete from t_user where id in(‘1,2,3’) 执行错误:1292 - Truncated incorrect DOUBLE value: ‘1,2,3’
使用${} :delete from t_user where id in(1, 2, 3)
/**
* 根据id批量删除
* @param ids
* @return
*/
int deleteBatch(String ids);
<delete id="deleteBatch">
delete from t_car where id in(${ids})
</delete>
@Test
public void testDeleteBatch(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
int count = mapper.deleteBatch("1,2,3");
System.out.println("删除了几条记录:" + count);
SqlSessionUtil.openSession().commit();
}
执行结果:
模糊查询
需求:查询奔驰系列的汽车。【只要品牌brand中含有奔驰两个字的都查询出来。】
使用${}
/**
* 根据品牌进行模糊查询
* @param likeBrank
* @return
*/
List<Car> selectLikeByBrand(String likeBrank);
<select id="selectLikeByBrand" resultType="Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
brand like '%${brand}%'
</select>
@Test
public void testSelectLikeByBrand(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectLikeByBrand("奔驰");
cars.forEach(car -> System.out.println(car));
}
执行结果:
使用#{}
第一种:concat函数
<select id="selectLikeByBrand" resultType="Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
brand like concat('%',#{brand},'%')
</select>
执行结果:
第二种:双引号方式
<select id="selectLikeByBrand" resultType="Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
brand like "%"#{brand}"%"
</select>
9.2 typeAliases
我们来观察一下CarMapper.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.powernode.mybatis.mapper.CarMapper">
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
order by carNum ${key}
</select>
<select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
car_type = '${carType}'
</select>
</mapper>
resultType属性用来指定查询结果集的封装类型,这个名字太长,可以起别名吗?可以。
在mybatis-config.xml文件中使用typeAliases标签来起别名,包括两种方式:
第一种方式:typeAlias
<typeAliases>
<typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/>
</typeAliases>
- 首先要注意typeAliases标签的放置位置,如果不清楚的话,可以看看错误提示信息。
- typeAliases标签中的typeAlias可以写多个。
- typeAlias:
- type属性:指定给哪个类起别名
- alias属性:别名。
<select id="selectAll" resultType="CAR">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
order by carNum ${key}
</select>
<select id="selectByCarType" resultType="car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
car_type = '${carType}'
</select>
运行测试程序:正常。
<a name="rNHAJ"></a>
## 9.3 mappers
SQL映射文件的配置方式包括四种:
- resource:从类路径中加载
- url:从指定的全限定资源路径中加载
- class:使用映射器接口实现类的完全限定类名
- package:将包内的映射器接口实现全部注册为映射器
<a name="ip4u6"></a>
### resource
这种方式是从类路径中加载配置文件,所以这种方式要求SQL映射文件必须放在resources目录下或其子目录下。
```xml
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
url
这种方式显然使用了绝对路径的方式,这种配置对SQL映射文件存放的位置没有要求,随意。
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
class
如果使用这种方式必须满足以下条件:
- SQL映射文件和mapper接口放在同一个目录下。
SQL映射文件的名字也必须和mapper接口名一致。
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
将CarMapper.xml文件移动到和mapper接口同一个目录下:
在resources目录下新建:com/powernode/mybatis/mapper【这里千万要注意:不能这样新建 com.powernode.mybatis.dao】
- 将CarMapper.xml文件移动到mapper目录下
- 修改mybatis-config.xml文件
<mappers>
<mapper class="com.powernode.mybatis.mapper.CarMapper"/>
</mappers>
运行程序:正常!!!
package
如果class较多,可以使用这种package的方式,但前提条件和上一种方式一样。<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="com.powernode.mybatis.mapper"/>
</mappers>
9.4 idea配置文件模板
mybatis-config.xml和SqlMapper.xml文件可以在IDEA中提前创建好模板,以后通过模板创建配置文件。
9.5 插入数据时获取自动生成的主键
前提是:主键是自动生成的。
业务背景:一个用户有多个角色。
插入一条新的记录之后,自动生成了主键,而这个主键需要在其他表中使用时。
插入一个用户数据的同时需要给该用户分配角色:需要将生成的用户的id插入到角色表的user_id字段上。
第一种方式:可以先插入用户数据,再写一条查询语句获取id,然后再插入user_id字段。【比较麻烦】
第二种方式:mybatis提供了一种方式更加便捷。/**
* 获取自动生成的主键
* @param car
*/
void insertUseGeneratedKeys(Car car);
<insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
@Test
public void testInsertUseGeneratedKeys(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Car car = new Car();
car.setCarNum("5262");
car.setBrand("BYD汉");
car.setGuidePrice(30.3);
car.setProduceTime("2020-10-11");
car.setCarType("新能源");
mapper.insertUseGeneratedKeys(car);
SqlSessionUtil.openSession().commit();
System.out.println(car.getId());
}
十、MyBatis参数处理
模块名:mybatis-006-param
表:t_student
表中现有数据:
pojo类:
```java
package com.powernode.mybatis.pojo;
import java.util.Date;
/**
- 学生类
- @author 老杜
- @version 1.0
- @since 1.0
*/
public class Student {
private Long id;
private String name;
private Integer age;
private Double height;
private Character sex;
private Date birth;
// constructor
// setter and getter
// toString
}
```
10.1 单个简单类型参数
简单类型包括:
- byte short int long float double char
- Byte Short Integer Long Float Double Character
- String
- java.util.Date
- java.sql.Date
需求:根据name查、根据id查、根据birth查、根据sex查
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Student;
import java.util.Date;
import java.util.List;
/**
* 学生数据Sql映射器
* @author 老杜
* @version 1.0
* @since 1.0
*/
public interface StudentMapper {
/**
* 根据name查询
* @param name
* @return
*/
List<Student> selectByName(String name);
/**
* 根据id查询
* @param id
* @return
*/
Student selectById(Long id);
/**
* 根据birth查询
* @param birth
* @return
*/
List<Student> selectByBirth(Date birth);
/**
* 根据sex查询
* @param sex
* @return
*/
List<Student> selectBySex(Character sex);
}
<?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.powernode.mybatis.mapper.StudentMapper">
<select id="selectByName" resultType="student">
select * from t_student where name = #{name}
</select>
<select id="selectById" resultType="student">
select * from t_student where id = #{id}
</select>
<select id="selectByBirth" resultType="student">
select * from t_student where birth = #{birth}
</select>
<select id="selectBySex" resultType="student">
select * from t_student where sex = #{sex}
</select>
</mapper>
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.StudentMapper;
import com.powernode.mybatis.pojo.Student;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class StudentMapperTest {
StudentMapper mapper = SqlSessionUtil.openSession().getMapper(StudentMapper.class);
@Test
public void testSelectByName(){
List<Student> students = mapper.selectByName("张三");
students.forEach(student -> System.out.println(student));
}
@Test
public void testSelectById(){
Student student = mapper.selectById(2L);
System.out.println(student);
}
@Test
public void testSelectByBirth(){
try {
Date birth = new SimpleDateFormat("yyyy-MM-dd").parse("2022-08-16");
List<Student> students = mapper.selectByBirth(birth);
students.forEach(student -> System.out.println(student));
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
@Test
public void testSelectBySex(){
List<Student> students = mapper.selectBySex('男');
students.forEach(student -> System.out.println(student));
}
}
通过测试得知,简单类型对于mybatis来说都是可以自动类型识别的:
- 也就是说对于mybatis来说,它是可以自动推断出ps.setXxxx()方法的。ps.setString()还是ps.setInt()。它可以自动推断。
其实SQL映射文件中的配置比较完整的写法是:
<select id="selectByName" resultType="student" parameterType="java.lang.String">
select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
</select>
其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助mybatis进行类型确定的。不过这些配置多数是可以省略的。因为mybatis它有强大的自动类型推断机制。
- javaType:可以省略
- jdbcType:可以省略
- parameterType:可以省略
如果参数只有一个的话,#{} 里面的内容就随便写了。对于 ${} 来说,注意加单引号。
10.2 Map参数
需求:根据name和age查询
/**
* 根据name和age查询
* @param paramMap
* @return
*/
List<Student> selectByParamMap(Map<String,Object> paramMap);
@Test
public void testSelectByParamMap(){
// 准备Map
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("nameKey", "张三");
paramMap.put("ageKey", 20);
List<Student> students = mapper.selectByParamMap(paramMap);
students.forEach(student -> System.out.println(student));
}
<select id="selectByParamMap" resultType="student">
select * from t_student where name = #{nameKey} and age = #{ageKey}
</select>
测试运行正常。
这种方式是手动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使用的时候通过#{map集合的key}来取值。
10.3 实体类参数
需求:插入一条Student数据
/**
* 保存学生数据
* @param student
* @return
*/
int insert(Student student);
<insert id="insert">
insert into t_student values(null,#{name},#{age},#{height},#{birth},#{sex})
</insert>
@Test
public void testInsert(){
Student student = new Student();
student.setName("李四");
student.setAge(30);
student.setHeight(1.70);
student.setSex('男');
student.setBirth(new Date());
int count = mapper.insert(student);
SqlSessionUtil.openSession().commit();
}
运行正常,数据库中成功添加一条数据。
这里需要注意的是:#{} 里面写的是属性名字。这个属性名其本质上是:set/get方法名去掉set/get之后的名字。
10.4 多参数
需求:通过name和sex查询
/**
* 根据name和sex查询
* @param name
* @param sex
* @return
*/
List<Student> selectByNameAndSex(String name, Character sex);
@Test
public void testSelectByNameAndSex(){
List<Student> students = mapper.selectByNameAndSex("张三", '女');
students.forEach(student -> System.out.println(student));
}
<select id="selectByNameAndSex" resultType="student">
select * from t_student where name = #{name} and sex = #{sex}
</select>
执行结果:
异常信息描述了:name参数找不到,可用的参数包括[arg1, arg0, param1, param2]
修改StudentMapper.xml配置文件:尝试使用[arg1, arg0, param1, param2]去参数
<select id="selectByNameAndSex" resultType="student">
<!--select * from t_student where name = #{name} and sex = #{sex}-->
select * from t_student where name = #{arg0} and sex = #{arg1}
</select>
运行结果:
再次尝试修改StudentMapper.xml文件
<select id="selectByNameAndSex" resultType="student">
<!--select * from t_student where name = #{name} and sex = #{sex}-->
<!--select * from t_student where name = #{arg0} and sex = #{arg1}-->
<!--select * from t_student where name = #{param1} and sex = #{param2}-->
select * from t_student where name = #{arg0} and sex = #{param2}
</select>
通过测试可以看到:
- arg0 是第一个参数
- param1是第一个参数
- arg1 是第二个参数
- param2是第二个参数
实现原理:实际上在mybatis底层会创建一个map集合,以arg0/param1为key,以方法上的参数为value,例如以下代码:
Map<String,Object> map = new HashMap<>();
map.put("arg0", name);
map.put("arg1", sex);
map.put("param1", name);
map.put("param2", sex);
// 所以可以这样取值:#{arg0} #{arg1} #{param1} #{param2}
// 其本质就是#{map集合的key}
注意:使用mybatis3.4.2之前的版本时:要用#{0}和#{1}这种形式。
10.5 @Param注解(命名参数)
可以不用arg0 arg1 param1 param2吗?这个map集合的key我们自定义可以吗?当然可以。使用@Param注解即可。这样可以增强可读性。
需求:根据name和age查询
/**
* 根据name和age查询
* @param name
* @param age
* @return
*/
List<Student> selectByNameAndAge(@Param(value="name") String name, @Param("age") int age);
@Test
public void testSelectByNameAndAge(){
List<Student> stus = mapper.selectByNameAndAge("张三", 20);
stus.forEach(student -> System.out.println(student));
}
<select id="selectByNameAndAge" resultType="student">
select * from t_student where name = #{name} and age = #{age}
</select>
通过测试,一切正常。
核心:@Param(“这里填写的其实就是map集合的key“)
10.6 @Param源码分析
十一、MyBatis查询语句专题
模块名:mybatis-007-select
打包方式:jar
引入依赖:mysql驱动依赖、mybatis依赖、logback依赖、junit依赖。
引入配置文件:jdbc.properties、mybatis-config.xml、logback.xml
创建pojo类:Car
创建Mapper接口:CarMapper
创建Mapper接口对应的映射文件:com/powernode/mybatis/mapper/CarMapper.xml
创建单元测试:CarMapperTest
拷贝工具类:SqlSessionUtil
11.1 返回Car
当查询的结果,有对应的实体类,并且查询结果只有一条时:
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
/**
* Car SQL映射器
* @author 老杜
* @version 1.0
* @since 1.0
*/
public interface CarMapper {
/**
* 根据id主键查询:结果最多只有一条
* @param id
* @return
*/
Car selectById(Long id);
}
<?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.powernode.mybatis.mapper.CarMapper">
<select id="selectById" resultType="Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where id = #{id}
</select>
</mapper>
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
public class CarMapperTest {
@Test
public void testSelectById(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Car car = mapper.selectById(35L);
System.out.println(car);
}
}
执行结果:
查询结果是一条的话可以使用List集合接收吗?当然可以。
/**
* 根据id主键查询:结果最多只有一条,可以放到List集合中吗?
* @return
*/
List<Car> selectByIdToList(Long id);
<select id="selectByIdToList" resultType="Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where id = #{id}
</select>
@Test
public void testSelectByIdToList(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectByIdToList(35L);
System.out.println(cars);
}
执行结果:
11.2 返回List
当查询的记录条数是多条的时候,必须使用集合接收。如果使用单个实体类接收会出现异常。
/**
* 查询所有的Car
* @return
*/
List<Car> selectAll();
<select id="selectAll" resultType="Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>
@Test
public void testSelectAll(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectAll();
cars.forEach(car -> System.out.println(car));
}
如果返回多条记录,采用单个实体类接收会怎样?
/**
* 查询多条记录,采用单个实体类接收会怎样?
* @return
*/
Car selectAll2();
<select id="selectAll2" resultType="Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>
@Test
public void testSelectAll2(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Car car = mapper.selectAll2();
System.out.println(car);
}
执行结果:
11.3 返回Map
当返回的数据,没有合适的实体类对应的话,可以采用Map集合接收。字段名做key,字段值做value。
查询如果可以保证只有一条数据,则返回一个Map集合即可。
/**
* 通过id查询一条记录,返回Map集合
* @param id
* @return
*/
Map<String, Object> selectByIdRetMap(Long id);
<select id="selectByIdRetMap" resultType="map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where id = #{id}
</select>
resultMap=”map”,这是因为mybatis内置了很多别名。【参见mybatis开发手册】
@Test
public void testSelectByIdRetMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Map<String,Object> car = mapper.selectByIdRetMap(35L);
System.out.println(car);
}
执行结果:
当然,如果返回一个Map集合,可以将Map集合放到List集合中吗?当然可以,这里就不再测试了。
反过来,如果返回的不是一条记录,是多条记录的话,只采用单个Map集合接收,这样同样会出现之前的异常:TooManyResultsException
11.4 返回List
查询结果条数大于等于1条数据,则可以返回一个存储Map集合的List集合。List
/**
* 查询所有的Car,返回一个List集合。List集合中存储的是Map集合。
* @return
*/
List<Map<String,Object>> selectAllRetListMap();
<select id="selectAllRetListMap" resultType="map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>
@Test
public void testSelectAllRetListMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Map<String,Object>> cars = mapper.selectAllRetListMap();
System.out.println(cars);
}
执行结果:
[
{carType=燃油车, carNum=103, guidePrice=50.30, produceTime=2020-10-01, id=33, brand=奔驰E300L},
{carType=电车, carNum=102, guidePrice=30.23, produceTime=2018-09-10, id=34, brand=比亚迪汉},
{carType=燃油车, carNum=103, guidePrice=50.30, produceTime=2020-10-01, id=35, brand=奔驰E300L},
{carType=燃油车, carNum=103, guidePrice=33.23, produceTime=2020-10-11, id=36, brand=奔驰C200},
......
]
11.5 返回Map
拿Car的id做key,以后取出对应的Map集合时更方便。
/**
* 获取所有的Car,返回一个Map集合。
* Map集合的key是Car的id。
* Map集合的value是对应Car。
* @return
*/
@MapKey("id")
Map<Long,Map<String,Object>> selectAllRetMap();
<select id="selectAllRetMap" resultType="map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>
@Test
public void testSelectAllRetMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Map<Long,Map<String,Object>> cars = mapper.selectAllRetMap();
System.out.println(cars);
}
执行结果:
{
64={carType=燃油车, carNum=133, guidePrice=50.30, produceTime=2020-01-10, id=64, brand=丰田霸道},
66={carType=燃油车, carNum=133, guidePrice=50.30, produceTime=2020-01-10, id=66, brand=丰田霸道},
67={carType=燃油车, carNum=133, guidePrice=50.30, produceTime=2020-01-10, id=67, brand=丰田霸道},
69={carType=燃油车, carNum=133, guidePrice=50.30, produceTime=2020-01-10, id=69, brand=丰田霸道},
......
}
11.6 resultMap结果映射
查询结果的列名和java对象的属性名对应不上怎么办?
```java
@Test
public void testSelectAllByResultMap(){
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = carMapper.selectAllByResultMap();
System.out.println(cars);
}
执行结果正常。
### 是否开启驼峰命名自动映射
使用这种方式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。
Java命名规范:首字母小写,后面每个单词首字母大写,遵循驼峰命名方式。
SQL命名规范:全部小写,单词之间采用下划线分割。
比如以下的对应关系:
| 实体类中的属性名 | 数据库表的列名 |
| —- | —- |
| carNum | car_num |
| carType | car_type |
| produceTime | produce_time |
如何启用该功能,在mybatis-config.xml文件中进行配置:
xml
<!--放在properties标签后面-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
```java
/**
查询所有Car,启用驼峰命名自动映射