Java 企业级应用开发 part 1 - J2EE part 1

记录一下 J2EE 的 SSM 学习

  • MyBatis
  • configuration
  • mappers

简介

MyBatis 是持久层的一个 ORM 框架,移去了 JDBC 的繁琐操作以及 SQL 对代码的强绑定,采用 xml 的 SQL mapper 文件解决了这个问题,并能够进行从 SQL 结果到 实体类的转换

简单使用

1
2
3
4
5
6
7
use mybatis;
create table users(
uid int primary key auto_increment,
uname varchar(20) not null,
uage int not null
);
insert into users(uid,uname,uage) values(null,'张三',20),(null,'李四',18);

这个例子是基于 Maven 构建的,首先是配置依赖,这里是老版本了,实际上可以在 这里 搜,然后直接 copy dependency

pom.xml

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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.itheima</groupId>
<artifactId>mybatistest</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!-- <scope>test</scope>-->
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>

</project>

这个 build part 实际上按这个目录写代码上不需要配置的

配置 MyBatis

db.properties
db.properties
1
2
3
4
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
mysql.username=root
mysql.password=mysql

应该不需要过多解释,数据库连接也就是这些东西

  • 指定驱动
  • 指定 JDBC URL,也就是 host, port 以及 database(可选)
  • 用户名 + 密码

这个属性名(整个等号前面)实际上是任意的,在这个版本里还没有自动注入执行,只是为了语义

mybatis-config.xml
mybatis-config.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 数据库连接相关配置 ,db.properties文件中的内容-->
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
<!-- mapping文件路径配置 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>

</mappers>

</configuration>

不在代码里配置,就得在 xml 里配置

<properties> 导入了我们的配置(记录的属性),随后的 <environments> 包含所有可能的环境,即不同的数据库配置,以及事务管理器配置,<dataSource> 中是主要的配置部分,将我们在 db.properties 中的属性加载过去

<mappers> 中配置了 mapper 部分,也即 SQL 到接口的映射配置,有几个 mapper 就有几条 <mapper>

environment 一个是通过 <environments>default 切换,一个是代码里构建 SqlSessionFactory 的时候指定

UserMapper.xml
UserMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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为映射的根节点-->
<!-- mapper为映射的根节点,namespace指定Dao接口的完整类名
mybatis会依据这个接口动态创建一个实现类去实现这个接口,
而这个实现类是一个Mapper对象-->
<mapper namespace="com.itheima.pojo.User">
<!--id ="接口中的方法名"
  parameterType="传入的参数类型"
  resultType = "返回实体类对象,使用包.类名"-->
<select id="findById" parameterType="int"
resultType="com.itheima.pojo.User">
select * from users where uid = #{id}
</select>
</mapper>

其中 <mapper> 中包含具体的映射关系(其实在 mybatis-config.xml 中我们就可以写这些,但是这样太耦合了,不推荐),这一元素必须要包含 namespace,这是为了解决 id 的命名空间问题,同一个 mapper 不能有相同的 id

  • id 是这一查询的接口名称(唯一标识)
  • parameterType 是传入参数类型
  • resultType 是传回结果类型,通常是一个 实体类,例如我们这里的 User
  • <select> 等标签内是一句动态解析的 SQL,不仅是参数可以动态,语句事实上也可以是动态的

关于这个参数传递,实际上对于这里 #{} 内部是任意的,这里是一个特殊情况,只有一个原子类型参数传递
对于一般情况,若传入类型直接为 实体类,那么 #{} 中的名称会直接解析为属性名,按照对象对应属性填充;若传入多个参数,且接口函数上无 @Param 注解标注,那么只能使用 #{0},#{1} 这样的顺序编号方式,按序读取参数;若传入接口函数上有注解标注或采用了 JVM 参数 -parameters 参数,则直接按函数接口上的命名对应填充
对于一个 mapper,在使用时我们更多讲它理解为一个函数,输入参数,生成 SQL 查询,返回结果,这也与他的接口设计模式相符合

User.sql
User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.itheima.pojo;

public class User {
private int uid; //用户id
private String uname; //用户姓名
private int uage; //用户年龄

public int getUid() {
return uid;
}

public void setUid(int uid) {
this.uid = uid;
}

public String getUname() {
return uname;
}

public void setUname(String uname) {
this.uname = uname;
}

public int getUage() {
return uage;
}

public void setUage(int uage) {
this.uage = uage;
}
}

这是实体类的定义,ORM 通过这些 getter, setter 函数来进行提取与修改,而避免直接对类成员的访问,这符合 OOP 的思想,且有更多参数的可操作性,例如对返回格式的调整以及参数验证

调用 MyBatis
UserTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package Test;

import com.itheima.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;

public class UserTest {
@Test
public void userFindByIdTest() {
//读取文件名
String resources = "mybatis-config.xml";
//创建流
Reader reader = null;
try {
//读取mybatis-config.xml文件内容到reader对象中
reader = Resources.getResourceAsReader(resources);
} catch (IOException e) {
e.printStackTrace();
}
//初始化mybatis数据库,创建SqlSessionFactory类的实例
SqlSessionFactory sqlMapper = new
SqlSessionFactoryBuilder().build(reader);
//创建SqlSession实例
SqlSession session = sqlMapper.openSession();
//传入参数查询,返回结果
User user = session.selectOne("findById", 2);
//输出结果
System.out.println(user.getUname());
//关闭session
session.close();
}
}

我们主要看这个类方法里面的代码,首先我们通过 Resources 类的 getResourceAsReader 方法,传入本地的配置文件路径来获取一个文件流的 Reader,并通过 SqlSessionFactoryBuilderbuild 方法构建一个 SqlSessionFactory

  • SqlSessionFactory 这个类是大多数 SQL 操作的开始

我们在这里首先通过 openSession 方法打开一个数据库 session(连接),再通过 session 上的 selectOne 方法通过 statemenet参数(id) 选出我们配置的 mapper 的一个接口,在这里,2 是传入参数,这只是单参数,单对象输出(One,对应的有 List)的传递方法,该方法调用 mapper 接口,返回了我们指定的类型

需要注意的一点是,有时候我们运行导入的项目会遇到找不到 mybatis-config.xml 的问题,这是因为 Java 编译很大程度靠 IDE(编译参数)的调节,有的项目没有在 pom.xml 中配置 resources 目录,会导致这一目录在编译后不被复制到编译目录中,导致这个问题发生
因此要么配置 resources,要么在 src/main/resources 目录上右键,选择 Mark Director As > Resources Root

核心配置

mapper 语法

除去一大堆关于 mybatis-config.xml 的克苏鲁选项以及 SqlSessionFactoryBuilderSqlSessionFactory,等的 大量api 之外,重要的其实只有 mapper 中的一些配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.itheima.mapper.StudentMapper">
<resultMap type="com.itheima.pojo.Student" id="studentMap">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="age" column="sage"/>
</resultMap>
<select id="findAllStudent" resultMap="studentMap">
select * from t_student
</select>
</mapper>

这个配置文件中,使用了一个 resultMap,这通常用于处理当数据库字段和实体类属性名称不一致的问题,我们通过这个 map 映射字段,在一个查询中,我们指定 resultMap 参数为我们 resultMapid

常用的查询类型有

  • <select>
  • <insert>
  • <update>
  • <delete>
  • <sql>
1
2
3
4
<insert id="addUser" parameterType="com.itheima.pojo.User"
keyProperty="uid" useGeneratedKeys="true" >
insert into users(uid,uname,uage) values(#{uid},#{uname},#{uage})
</insert>

这个 insert 中有几个特殊的字段,分别是 keyPropertyuseGeneratedKeys,这是用于获取指定自动增长字段的值并写回传入对象的 uid 属性中

这常在我们做注册操作时使用,在注册时,用户 id 往往自动生成,但是我们可能需要获取这些 id 进行一些后续步骤,而避免二次查询

SqlSessionFactory 封装

在一个项目中,这部分往往可以封装并复用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.itheima.utils;

import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

/**
* 工具类
*/
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory = null;

// 初始化SqlSessionFactory对象
static {
try {
// 使用MyBatis提供的Resources类加载MyBatis的配置文件
Reader reader =
Resources.getResourceAsReader("mybatis-config.xml");
// 构建SqlSessionFactory工厂
sqlSessionFactory =
new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}

// 获取SqlSession对象的静态方法
public static SqlSession getSession() {
return sqlSessionFactory.openSession();
}
}

动态 SQL

一切都是为了不手动拼接字符串