序中序
本系列旨在探讨mybatis底层逻辑,顺便以源码出发,探究Java语言的若干机制。
同时,此系列采取工程代码开发中敏捷的原则,不会像以往一样动辄写数千字,而是以“小步快跑”的形式,快速对自己所学的知识作出反应,总结、分享出来。
本文的第一、二节讲述一些网页应用,ORM和持久化的基本概念,第三节会讲一个最简单的mybatis使用的实例(正如网上大部分博客那样)。
一、网页应用
以简书一个不知名博主的主页为例:https://www.jianshu.com/u/6d6837a8715a
当我们在浏览器输入上面这个链接,按下回车的时候,浏览器就会根据这个链接向相关的服务器请求数据(详情可见《无线世界》系列)。服务器会返回html,js和css文件给我们的电脑,我们电脑上的浏览器会解析这些文件,并且将简书的页面展示给我们看。
这种模式被成为浏览器/服务器模式(B/S Browser/Server)。另外一种常见的叫做客户端/服务器模式(C/S Client/Server),咱也不懂,咱也不去说。
但是简书上的博主、用户这么多,简书不可能为每一个人都单独建立一套html/js/css文件,于是采取了比较讨巧的做法:只用一份html/js/css文件来用作页面的展示,而将用户名,文章,动态等信息存进数据库中,为每一个用户配置一个唯一id,用这个id来关联用户的数据。
这就可以引申到MVC(Model-View-Control)模型的基本思想。M指业务模型,V指用户界面,C指控制器(用于业务模型和用户界面的同步等)。控制器处理用户的入参(比如上例中简书链接最后的id),控制器会根据入参到业务模型中存取数据(上例中就是取出用户“宫本花藏”的文章、评论等信息),最后在用户界面上予以展示(如上上图所示)。
二、持久化框架与ORM
在面向对象中,有三层架构的思想。对于服务器来说,用户能够接触到的往往是最外的表现层,使用MVC模式来接收用户数据,或者向用户展示数据;服务层用户用户数据的逻辑处理;而最里面的持久层用于和数据库打交道,负责数据的增删改查。
人们都说mybatis是持久层框架,就是说mybatis这个框架,是专门用来让Java程序和数据库交互的。
如果说谁发明了一个服务层框架,就是说他设计的这个框架是专门用来处理业务逻辑的。emmm思考题:这么说来,那些算法的jar包看来都能算成服务层框架了???
众所周知,服务器能正常运行是要靠代码部署在上面的。Java是常用的服务器逻辑代码。如果想要通过Java访问数据库,就必须使用到Java中的JDBC接口。传统的JDBC连接数据库方式很繁琐,要先注册数据库驱动类,确认url,账号密码,再通过DriverManager打开数据库连接,将拼接好的SQL语句以字符串的形式给Statement,得到执行结果后手动关闭数据库链接。
为了解决该问题,ORM(Object Relation Mapping,对象-关系映射)框架应运而生。java代码可以根据映射配置文件,完成数据在对象模型(Java语言)与关系模型(SQL语言)之间的映射(比如java中的String类可以转换成SQL中的VARCHAR数据类型)。
另外,频繁的新建、关闭数据库连接会极大的消耗资源,成为系统的性能瓶颈。ORM框架的另外一个特色功能就是建立数据库连接池:专门创建若干给数据库连接,当某进程有连接请求时就将连接分配给该进程,用完了也不释放连接,而是将连接对象再放进池子里面来,供别的进程使用。
在书写代码的过程中,千千万万的程序员上演着可歌可泣的与bug斗智斗勇的故事,闻者流泪,听者伤心。代码既要处理用户输入,又要执行业务逻辑,存取数据;同时内要考虑负载均衡进程冲突资源抢占,外要预防非法输入网络攻击安全漏洞,忙不胜收。
于是,伟大的程序员先贤们针对代码混乱的问题,建立了各种框架。这些框架划分不同功能的代码,让它们各司其职。这些代码大多可以不依赖其他服务独立运行,并且提供某些特定服务。随着时代的发展,不同功能的代码还会划分层次,分出哪些用于公共服务,哪些用于应用实现。
比如上面这个,分成了基础支持层,核心处理层和接口层3层。每一层各个模块的功能目的都不相同。这个框架就是mybatis整体的框架,也是接下来我们的探索旅程的全景图。
不过由于本文是序文,所以本文只是给了一个最简单的mybatis使用的示例。
三、mybatis示例
所需:mysql数据库,集成开发环境IDEA(社区版即可),maven,预先装好的JDK1.8,一个能联网的环境
3.1 MySQL
既然mybatis是与数据库打交道的框架,就必须安装数据库。以免费的mysql为例,需要先注册ORACLE账户,登录进行下载。没有ORACLE账户的需要注册一个,是免费的。
mysql向导链接:https://www.mysql.com/cn/why-mysql/white-papers/visual-guide-to-installing-mysql-windows-zh/
安装的时候需要配置用户名和密码,按它给的pdf文档来即可。安装完成后,打开的界面如下所示:
点击 Local instance 字样的方框,就可以进入到页面中
进入数据库后,需要先创建schema。schema可以理解为是一个用户,一个用户可以拥有多张数据表。不同用户之间的数据表不同,并且有访问权限控制。输入以下语句创建用户:
create schema sjjdata default character set utf8 collate utf8_general_ci;
选择该用户,创建一个包含id,姓名,年龄3个字段的表,表的名字叫做student(是不是很俗)
use sjjdata;
create table student
(id int(11),
name varchar(25),
age int(11));
最终我们在mysql数据库中,创建了一个名为sjjdata的用户,在这个用户下新建了一张名为student的表。
3.2 Maven
程序语言发展至今,我们编程的时候再也不可能从零开始写。我们会依靠前人的编程成果,即调用代码库的形式,提升我们的编程效率。在Java中,我们通常以使用jar包的形式,调用前人写好的方法,而忽略实现该方法的细节(这种行为也被成为引用依赖)。jar包是一群java文件和配置文件的集合。除了jar包以外,随着规模的不同,还有tar包,war包。一个jar包举例:
发布jar包的团队或个人通常会每隔一段时间更新这个包, 修复一些bug或者新增一些功能。为了便于区分,他们为每一次发布的jar包取了一个版本号,比如上图中的mybatis包的版本是3.2.7。
其他程序员在编写程序引用jar包的时候,常常因为jar包的版本不对而引发bug。为了解决此类问题,有很多专门解决依赖的工具,maven就是其中一个。它通过xml文件(往往这个文件的名字叫做pom.xml)确定具体引用哪个版本的哪个包,就可以从它的maven仓库中取出此包,供程序员调用。这么做的好处是在程序移植的时候,只要把对应的xml文件也移植过去,其他机器上就算没有这个jar包,或者没有特定版本的jar包,也可以通过xml文件到maven仓库中找到它。
maven是apache的开源项目,可以在官网下载。https://maven.apache.org
下载过来的是一个zip文件,解压,随便放在某个目录就可以。为了避免不必要的字符编码问题,建议放在英文路径下。
当然,有时候IDEA自己在安装的时候可能已经自带了maven了……
3.3 Java
IDEA也有官网,也有免费使用的版本,奉上链接:
https://www.jetbrains.com/products.html#type=ide
笔者下载的是2019.3版本,打开界面如下所示:
进入后,点击file-->new-->project,选择maven
点击next,确定工程名,直接finish
在工程的出生地,就可以发现布局的很有规律的目录,以及一个pom.xml文件,这些都是maven规范自动生成的。
在xml文件中,贴入如下代码。其含义将在之后逐渐解释。
<?xml version="1.0"encoding="UTF-8"?>
<projectxmlns="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.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SecondMybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!-- <scope>test</scope>-->
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
</dependencies>
</project>
复制完之后,程序右下角会弹出这样的对话框:
点击Import Changes,就可以加载pom.xml指定的jar包
按照如下格式建立目录(直接右键文件夹,new,选择java或者package,这里不再赘述):
在UserBeanMapper中,写入如下代码:
package com.sjj.dao;
import com.sjj.entity.UserBeanVO;
import java.util.List;
public interface UserBeanMapper {
UserBeanVO queryUserByName(String name);
List<UserBeanVO> queryAll();
int insertUser(UserBeanVO userBean);
int deleteUserByName(String name);
int updateUserById(UserBeanVO userBean,int id);
}
在UserBeanVO中,写入如下代码:
package com.sjj.entity;
/**
* @author jun
* @date 2019/1/31
*/
public class UserBeanVO {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
在UserBeanService中,写入如下代码:
package com.sjj.service;
public class UserBeanService {
}
在DBTools中,写入如下代码:
package com.sjj;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
/**
* @author jun
* @date 2019/1/31
*/
public class DBtools {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
Reader reader = Resources.getResourceAsReader("config/SqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}
在mysql.properties中,写入如下代码(注意,这里需要根据没人配置的不同有所更改):
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/sjjdata?useSSL=false
jdbc.username=你的账号,一般是root
jdbc.password=你的密码,安装的时候你自己配的
在SqlMapperConfig.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文件 -->
<properties resource="config/mysql.properties"></properties>
<!-- 全局配置 -->
<settings>
<!-- 打开延迟加载开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消极加载即按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 定义别名 -->
<typeAliases>
<!-- 一个一个地定义 -->
<!-- <typeAlias type="com.gjh.domain.User" alias="user"/> -->
<!-- 指定包名定义,别名就是类名,首字母大小写都可以 -->
<package name="com.sjj.entity"/>
</typeAliases>
<!-- 和spring整合后 environments将废除 -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC事务管理,事务控制由mybatis,另一种类型为MANAGED,此配置从来不提交或回滚事务 -->
<transactionManager type="JDBC"/>
<!-- 数据库连接池,由mybatis type的值有UNPOOLED、POOLED、JNDI三种数据源
不同的数据源有不同的配置属性 -->
<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与接口类名一致且在同一个路径下时可以用class引入 -->
<!-- <mapper class="com.sjj.dao.UserBeanMapper"/>-->
<mapper resource="mapper/UserBeanMapper.xml"/>
<!-- 通过配置文件的位置加载——一次加载一个 -->
<!-- <mapper class="com.gjh.mapper.OrdersMapper"/>-->
<!-- 通过包名加载——加载包内的所有配置文件-->
<!-- <package name="mapper"/>-->
</mappers>
</configuration>
在log4j2.xml中,写入如下代码:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</Console>
<RollingFile name="RollingFile" fileName="logs/strutslog1.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d{MM-dd-yyyy} %p %c{1.} [%t] -%M-%L- %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="1 KB"/>
</Policies>
<DefaultRolloverStrategy fileIndex="max" max="2"/>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.opensymphony.xwork2" level="WAN"/>
<Logger name="org.apache.struts2" level="WAN"/>
<Root level="warn">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
在appTest中,写入如下代码:
package com.sjj.test;
import com.sjj.DBtools;
import com.sjj.dao.UserBeanMapper;
import com.sjj.entity.UserBeanVO;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
public class appTest {
public static void main(String[] args){
//1.创建sqlsessionFactory
SqlSessionFactory sqlSessionFactory = DBtools.getSqlSessionFactory();
//2.创建SqlSession
SqlSession session = sqlSessionFactory.openSession();
//3.session 中创建相应的接口代理类,即mapper对象
UserBeanMapper userBeanMapper = session.getMapper(UserBeanMapper.class);
System.out.println(userBeanMapper.queryAll());
try {
System.out.println(userBeanMapper.deleteUserByName("jun"));
UserBeanVO u1 = new UserBeanVO();
u1.setAge(16);
u1.setName("Nausicaa");
u1.setId(1);
System.out.println(userBeanMapper.insertUser(u1));
session.commit();//一定要提交,不然所有增删改操作不会生效的
System.out.println(userBeanMapper.queryAll());
}catch (Exception e){
session.rollback();//回滚
}
}
}
最终运行程序,跑出的结果如下所示:
去MySQL里面查,可以发现,上一篇推送《读书笔记6:<风之谷>》中的主角,娜乌西卡的信息,已经被登记到了数据库之中。
四、总结
谢特,怎么又有这么多字……