1.1 软件开发常用结构
1.1.1 三层架构
三层架构包含的三层:
界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据
访问层(Data access layer)
三层的职责
1. 界面层(表示层,视图层):主要功能是接受用户的数据,显示请求的处理结果。使用 web 页面和用户交互,手机 app 也就是表示层的,用户在 app 中操作,业务逻辑在服务器端处理。
2. 业务逻辑层:接收表示传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
3. 数据访问层:与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库.
每一层对应着一个框架:
(1)界面层---SpringMVC框架
(2)业务层---Spring框架
(3)持久层---MyBatis框架
三层的处理请求的交互:
用户---> 界面层--->业务逻辑层--->数据访问层--->DB 数据
为什么要使用三层?
1,结构清晰、耦合度低, 各层分工明确
2,可维护性高,可扩展性高
3,有利于标准化
4,开发人员可以只关注整个结构中的其中某一层的功能实现
5,有利于各层逻辑的复用
使用JDBC编程的基本代码步骤:
public void findStudent() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//注册mysql 驱动
Class.forName("com.mysql.jdbc.Driver");
//连接数据的基本信息 url ,username,password
String url = "jdbc:mysql://localhost:3306/springdb";
String username = "root";
String password = "123456";
//创建连接对象
conn = DriverManager.getConnection(url, username,
password);
//保存查询结果
List<Student> stuList = new ArrayList<>();
//创建Statement, 用来执行sql 语句
stmt = conn.createStatement();
//执行查询,创建记录集,
rs = stmt.executeQuery("select * from student");
while (rs.next()) {
Student stu = new Student();
stu.setId(rs.getInt("id"));
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
//从数据库取出数据转为Student 对象,封装到List 集合
stuList.add(stu);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭资源
if (rs != null) ;
{
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用JDBC缺陷
1. 代码比较多,开发效率低
2. 需要关注 Connection ,Statement, ResultSet 对象创建和销毁
3. 对 ResultSet 查询的结果,需要自己封装为 List
4. 重复的代码比较多,业务代码和数据库的操作混在一起
MyBatis 解决的主要问题
1. 注册数据库的驱动,例如 Class.forName(“com.mysql.jdbc.Driver”))
2. 创建 JDBC 中必须使用的 Connection , Statement, ResultSet 对象
3. 从 xml 中获取 sql,并执行 sql 语句,把 ResultSet 结果转换 java 对象
MyBatis实现步骤:
0.创建student表(id,name,email,age)
1.新建maven项目
2.修改pom.xml
1)加入依赖 mybatis依赖, mysql驱动, junit
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--juint-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
2)在<build>加入资源插件
需要在pom.xml中的<build>中加入下面这个插件,这样在dao目录下可以直接创建对应 的.xml文件,否则需要在resouces目录中创建和dao接口对应的目录,在对应的目录下面创建对应的.xml文件
<build>
<!--资源插件: 处理src/main/java目录中的xml-->
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
3.创建实体类Student。定义属性, 属性名和列名保持一致
4.创建Dao接口, 定义操作数据库的方法。
5.创建xml文件(mapper文件), 写sql语句。
<mapper namespace="com.rk.mapper.UserMapper">:通过<mapper>标签的namespace找 到UserMapper
mybatis框架推荐是把sql语句和java代码分开
mapper文件:定义和dao接口在同一目录, 一个表一个mapper文件。
6.创建mybatis的主配置文件(xml文件):有一个, 放在resources目录下
1)定义创建连接实例的数据源(DataSource)对象
2) 指定其他mapper文件的位置
在Mybatis-com.xml映射UserMapper.xml:
<mapper resource="com/rk/mapper/UserMapper.xml"/>:注意这里是/ UserMapper.xml用 的是 . 如果想用点则使用class属性:<mapper class="com.rk.mapper.UserMapper"></mapper>
通过mybatis-config.xml中的<mappers> -----> 找到UserMapper.xml -----> UserDao
创建mybatis的主配置文件和mapper文件需要导入约束,约束在mybatis官网有,代码如下
主配置文件:
<?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">
mapper文件:
<?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">
打印执行日志的插件(放在mybatis的主配置中):
<!--设置mybatis输出日志-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
mybatis实现步骤:
1、读取配置文件
2、利用SqlSessionFactoryBuilder()对象加载读取的配置文件获取factory
3、利用factory的openSession方法获取SqlSession对象,factory.openSession(true),设置参 数为true为自动提交事务,这样后面不需要手动提交session.commit()
4、SqlSession创建Dao接口的代理对象
5、使用代理对象执行方法
6、释放资源
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
UserDao userDao = session.<UserDao>getMapper(UserDao.class);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
Mybatsi的工具类
public class MyBatisUtil {
//定义 SqlSessionFactory
private static SqlSessionFactory factory = null;
static {
//使用 静态块 创建一次 SqlSessionFactory
try{
String config = "mybatis-config.xml";
//读取配置文件
InputStream in = Resources.getResourceAsStream(config);
//创建SqlSessionFactory 对象
factory = new SqlSessionFactoryBuilder().build(in);
}catch (Exception e){
factory = null;
e.printStackTrace();
}
}
/* 获取SqlSession 对象 */
public static SqlSession getSqlSession(){
SqlSession session = null;
if( factory != null){
session = factory.openSession();
}
return session;
}
使用mybatis代理要求
(1)mapper文件中的namespace 一定dao接口的全限定名称
(2)mapper文件中 标签的id是dao接口方法名称
Dao 接口中方法的参数只有一个简单类型(java 基本类型和 String),占
位符 #{ 任意字符 },和方法的参数名无关。
多个参数的传递:
方式一:
当 Dao 接口方法多个参数,需要通过名称使用参数。 在方法形参前面加
入@Param(“自定义参数名”),mapper 文件使用#{自定义参数名}。
dao接口方法:
List<Student> selectMultiParam(@Param("personName") String name,
@Param("personAge") int age);
mapper 文件:
<select id="selectMultiParam" resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student where name=#{personName} or age=#{personAge}
</select>
方式二:使用对象
创建保存参数值的对象 QueryParam
package com.bjpowernode.vo;
public class QueryParam {
private String queryName;
private int queryAge;
//set ,get 方法
}
接口方法:
List<Student> selectMultiObject(QueryParam queryParam);
mapper 文件:
<select id="selectMultiObject" resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student where name=#{queryName} or age=#{queryAge}
</select>
方式三:按位置
参数位置从 0 开始, 引用参数语法 #{ arg 位置 } , 第一个参数是#{arg0},
第二个是#{arg1}
接口方法:
List<Student> selectByNameAndAge(String name,int age);
mapper 文件:
<select id="selectByNameAndAge"resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student where name=#{arg0} or age=#{arg1}
</select>
#:占位符,告诉 mybatis 使用实际的参数值代替。并使用PrepareStatement 对象执行 sql 语句, #{...}代替 sql 语句的“?”。这样做更安全,更迅速,通常也是首选做法
$ 字符串替换,告诉 mybatis 使用$包含的“字符串”替换所在位置。使用Statement 把 sql 语句和${}的内容连接起来。主要用在替换表名,列名,不同列排序等操作。
resultType: 执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。resultType 和 resultMap不能同时使用。
1. 调用com.bjpowernode.domain.Student的无参数构造方法,创建对象。Student student = new Student(); //使用反射创建对象
2. 同名的列赋值给同名的属性。
student.setId( rs.getInt("id"));
student.setName(rs.getString("name"));
3. 得到java对象, 如果dao接口返回值是List集合, mybatis把student对象放入到List合。
实体类属性名和列名不同的处理方式 :
1、使用别名:
<select id="selectUseFieldAlias"resultType="com.bjpowernode.domain.PrimaryStudent">
select id as stuId, name as stuName,age as stuAgefrom student where name=#{queryName} or age=#{queryAge}
</select>
2、使用<resultMap>
1. 接口方法
List<PrimaryStudent> selectUseDiffResultMap(QueryParam param);
2. mapper 文件:
<!-- 创建resultMap
id:自定义的唯一名称,在<select>使用
type:期望转为的java 对象的全限定名称或别名
-->
<resultMap id="primaryStudentMap"
type="com.bjpowernode.domain.PrimaryStudent">
<!-- 主键字段使用id -->
<id column="id" property="stuId" />
<!--非主键字段使用result-->
<result column="name" property="stuName"/>
<result column="age" property="stuAge" />
</resultMap>
<!--resultMap: resultMap 标签中的id 属性值-->
<select id="selectUseDiffResultMap" resultMap="primaryStudentMap">
select id,name,email,age from student
where name=#{queryName} or age=#{queryAge}
</select>
模糊查询like
模糊查询的实现有两种方式
一是 java 代码中给查询数据加上“%” ;
二是在 mapper 文件 sql 语句的条件位置加上“%”
<select id="selectLikeSecond" resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student
where name like '%${studentName}%'
</select>
MyBatis 框架动态 SQL
动态sql: 同一个dao的方法, 根据不同的条件可以表示不同的sql语句, 主要是where部分有变化使用mybatis提供的标签,实现动态sql的能力
在 mapper 的动态 SQL 中若出现大于号(>)、小于号(<)、大于等于号(>=),小于等于号(<=)等符号,最好将其转换为实体符号。否则,XML 可能会出现解析出错问题。
特别是对于小于号(<),在 XML 中是绝不能出现的。否则解析 mapper文件会出错。
<if>:对于该标签的执行,当 test 的值为 true 时,会将其包含的 SQL 片断拼接
到其所在的 SQL 语句中。
语法:<if test=”条件”> sql 语句的部分 </if>
mapper 文件:
<select id="selectStudentIf" resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student
where 1=1
<if test="name != null and name !='' ">
and name = #{name}
</if>
<if test="age > 0 ">
and age > #{age}
</if>
</select>
<where>:<if/>标签的中存在一个比较麻烦的地方:需要在 where 后手工添加 1=1的子句。因为,若 where 后的所有<if/>条件均为 false,而 where 后若又没有 1=1 子句,则 SQL 中就会只剩下一个空的 where,SQL 出错。所以,在where 后,需要添加永为真子句 1=1,以防止这种情况的发生。但当数据量很大时,会严重影响查询效率。
使用<where/>标签,在有查询条件时,可以自动添加上 where 子句;没有查询条件时,不会添加 where 子句。需要注意的是,第一个<if/>标签中的SQL 片断,可以不包含 and。不过,写上 and 也不错,系统会将多出的 and去掉。但其它<if/>中 SQL 片断的 and,必须要求写上。否则 SQL 语句将拼接出错
语法:<where> 其他动态 sql </where>
<foreach>:<foreach/>标签用于实现对于数组与集合的遍历
注意:
collection 表示要遍历的集合类型, list ,array 等。
open、close、separator 为对遍历内容的 SQL 拼接。
语法:
<foreach collection="集合类型" open="开始的字符" close="结束的字符"
item="集合中的成员" separator="集合成员之间的分隔符">
#{item 的值}
</foreach>
案例:
需求:查询学生 id 是 1002,1005,1006
接口方法:
List<Student> selectStudentForList(List<Integer> idList);
mapper 文件:
<select id="selectStudentForList"
resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student
<if test="list !=null and list.size > 0 ">
where id in
<foreach collection="list" open="(" close=")"
item="stuid" separator=",">
#{stuid}
</foreach>
</if>
</select>
测试方法:
@Test
public void testSelectForList() {
List<Integer> list = new ArrayList<>();
list.add(1002);
list.add(1005);
list.add(1006);
List<Student> studentList = studentDao.selectStudentForList(list);
studentList.forEach( stu -> System.out.println(stu));
}
mybatis主配置文件中的mappers:
<mappers>
<!--第一种方式, resources="mapper文件的路径"
优点:文件清晰。 加载的文件是明确的。
文件的位置比较灵活。
缺点:文件比较多, 代码量会比较大, 管理难度大
-->
<mapper resource="com/bjpowernode/dao/StudentDao.xml"/>
<mapper resource="com/bjpowernode/dao/OrderDao.xml"/>
<!--
第二种方式,使用<package>
name:包名, mapper文件所在的包名。
特点: 把这个包中的所有mapper文件,一次加载。
使用要求:
1. mapper文件和dao接口在同一目录
2. mapper文件和dao接口名称完全一样。
-->
<package name="com.bjpowernode.dao" />
<package name="com.bjpowernode.dao1" />
</mappers>
Mybatis 通用分页插件 PageHelper
使用步骤:
(1)maven 坐标(加入依赖)
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
(2)加入 plugin 配置
在主配置文件<environments>之前加入
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>
(3)PageHelper 对象
在select语句之前,调用PageHelper.startPage(页码, 每页大小)
查询语句之前调用 PageHelper.startPage 静态方法。除了PageHelper.startPage 方法外,还提供了类似用法的 PageHelper.offsetPage 方法。在你需要进行分页的 MyBatis 查询方法前调用 PageHelper.startPage 静态方法即可,紧跟在这个方法后的第一个MyBatis 查询方法会被进行分页
@Test
public void testSelect() throws IOException {
//获取第1 页,3 条内容
PageHelper.startPage(1,3);
List<Student> studentList = studentDao.selectStudents();
studentList.forEach( stu -> System.out.println(stu));
}