持久层 再学习

JDBC-Java DataBase Connect

JDBC就是java数据库连接的意思,java连接数据库都得使用JDBC技术,这个技术实现的是对于不同的数据库系统,jdbc都提供了类似的接口供java应用程序调用,而不同数据库系统的差异部分,通过不同的数据库开发的java连接依赖来确定

例如mysql的连接就需要依赖mysql-connector-java

JDBC的设计结构

JDBC通过几个关键的类完成了连接数据库,操作数据库等功能

  1. DriverManager
  2. Connection
  3. Statement
  4. ResultSet
  5. PreparedStatement

其中分工是这样的

DrvierManager负责和数据库如何建立连接打交道

Connection负责和数据库维持通信

Statement是connect中的核心功能,负责发送操作数据库的语句

ResultSet是在Statement操作之后,向数据库查询结果的对象,实际上数据并不在这个里面,而是需要读取的时候才会从数据库哪里申请来看,一旦关闭数据就消失了

PreparedStatement这个是对statement的升级,提供了更安全的sql语句支持

工作流程

我们想要和任何一个数据库建立连接,当然就是要负责建立连接的JDBC API来操作了

DriverManager的工作

首先我们在使用DriverManager的静态方法之前,需要这个Manager知道我们需要和那种数据库连接?

Manager你需要去找到对应数据库的集体连接方法,这个方法通常在不同的数据库实现中使用名为Driver的类实现

例如mysql数据库,连接mysql数据库的具体类就是com.mysql.cj.jdbc.Driver

所以在使用DriverManager之前有一个工作,就是

1 传入具体负责建立连接对象的Driver

DriverManger.registerDriver(Driver) 实际上DriverManager就是调用的register传入的Driver对象来建立连接的

一般是通过Class.forName(“具体实现类的classPath”)来为DriverManager传入Driver的,因为具体的实现类也知道要让DriverManager知道我,所以在自己的类中设置了静态代码块

1
2
3
4
5
6
7
8
9
public class Driver{
...
static{
try{
DriverManager.registerDriver(new Driver());
}catch(...)...

}
}

2 获取数据库连接

想要和数据库建立连接获得Connection?

数据库系统在哪里?数据库叫什么名字?用户名密码?需不需要开启事务…

这些信息都是建立连接时要考虑的,因为有些功能我们开启,那么数据库对我们传递的信息会进行更加复杂的解析流程,而如果不我们开启这些功能,那么数据库反应就很快,所以建立连接这个工作也是需要很多参数来确定的

实际上这些参数我们可以使用一个字符串来全部表示,这个字符串遵从数据库连接格式,这个就是jdbc url格式

jdbc url语法

url分为不同的部分

<>第一部分 协议部分:

jdbc:mysql://

解析:

第一个协议就是jdbc,第二个协议就是具体的数据库连接

可见协议就是 带上冒号的

使用//来表示协议结束

<>第二部分 数据库的定位信息:
ip:port/数据库名

解析:
通过/斜线来分割不同的层次

<>第三部分 参数部分:
?key=value&key=value….

解析:

这个部分通过?起头

通过key=value的键值对形式传递参数信息

通过&分隔不同的参数信息

url常用的参数

characterEncoding=utf-8

告诉数据库我们的发送的信息编码是utf-8

userSSL=false

告诉数据库不要叫我们使用SSL安全连接方式了

user=test

告诉数据库登录的用户名是test

password=123

告诉数据库我们的登录密码是123

具体的方法

使用DriverManager类的静态方法

getConnection( some parameters)

就可以获得连接

这个方法有很多重载,主要用到的形式就是:

  1. getConnection(url)
  2. getConnection(url,user,password)

Connection连接的工作

3 使用连接与数据库协商要执行语句的线程

实际上,不并不是connection中发送指令然后mysql就会去执行的,connection主要的作用是与数据库进行协商

mysql也不能随时接受命令进行执行,而是当有空余时间才会允许执行客户端的语句

等到数据库同意执行客户端语句之后,会开辟一个线程专门用来处理来自客户端的执行语句

并且在connection请求执行语句的时候,有几种选择,一个钟普通的statement也就是mysql最敷衍的执行语句,这种mysql对于这种类型来的语句都不会过多检查,直接干完了事,而preparedStatement的执行语句类型,mysql会去执行拼接操作,确保语句是安全可靠的

connection最常见的协商内容
  1. 事务的协商

我们的sql命令默认都是发送一条执行一条的,如果我们想使用事务

我们可以开启事务,这个就需要我们的connection去通知数据库,statement只能发送sql语句

conn.setAutoCommit(boolean)

调用这个方法,connection就会告诉数据库,我要求我的语句都按照事务处理也就是满足ACID的要求来执行

如果传入参数是true表示每一条语句都自动提交,也就是每一条语句都是单独的一个事务,这样会造成数据库的开销很大

传入的参数是false那就需要手动提交事务了

mysql此时接收到sql命令不会立即执行,而是将sql加入到一个队列中,等到接收到connect通知的提交命令才一起按照一个事务的代码执行

这个命令就是conn.commit()

如果有某个命令发生异常,我们也需要取消事务,此时需要通知数据库出现错误了,你赶紧根据队列中的sql指令反推回复代码,把之前做到一半的事情给回复到没做之前

conn.rollback()

经典的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
try{
//开启事务
conn.setAutoCommit(false);
//发送sql语句
statement.execute(...);
...
//提交事务
conn.commit();
}catch(Exception e){
//回滚
conn.rollback();
}

statement的工作

4 用sql发送对象发送sql给数据库

实际上这个statement就是一个发送sql的对象,没有问题

这个对象最为常用的方法有这些:

  1. execute(“sql string”)

这个方法返回的值是一个ResultSet类型,实际上数据在数据库端,我们只是使用这个ResultSet去数据库查询结果罢了

executeQuery也是这个返回值

注意statement的返回值,根据不同的操作,语句的执行结果返回值不同,这样什么关系呢?

如果是可预知的返回结果,那我们可以直接从数据库获取过来

如果是类似查询语句的结果,那么我们没法知道这个查询结果到底有多大,我们只能通知数据库,先传一部分过来,一点一点的查看

所以就有了下面的方法

  1. executeUpdate(“”)

这个方法返回就是一个int测值,表示这条语句影响的数据

  1. executeInsert(“”)

这个也是返回int的值

  1. 批处理,类似事务的一次性执行多条语句,但是不用事务的要求

statement.addBatch( “sql String”) 添加一条语句到我们客户端的队列中

statement.addBatch …

向服务器提交这一批语句

statement.executeBatch() 返回的是一个int[],表示每条语句的执行情况

临时不想执行批次

调用statement.clearBatch()清空当前的batch列表

preparedStatement的工作

实际上preparedstatement就是我们的statement,但是数据库对于prepared语句传入的参数会进行拼装来阻止sql注入的攻击

conn.getPreparedStatement(“sql template”)

获得preparedstatement语句对象

preparedstatement的语句组织形式

preparedstatement并不是直接将拼接好的sql语句传递给数据库

而是提供一个sql模版,这个sql模版语句告诉了数据库此语句的基本逻辑是什么

然后在传输模版的一些实际传参,数据库用这些实际传参来替代模版中缺失的具体数据部分这样来确定最终的执行语句

这个方法是如何阻止sql注入的呢?

可以看出,这种方式使得sql语句的逻辑,和具体数据相分离,而sql注入就是想要将具体数据变成逻辑部分,从而改变我们的sql语句的逻辑

具体的语法

1 获取preparedStatement

需要准备好我们的sql模版,然后使用conn.preparedStatement(sql_template) 才能获取到

2 传递参数

通过下标来传递参数

statement.setObject(index,参数)

statement.setString(index,参数)

可以使用多种类型的传参方法,让数据库正确理解传入的参数类型方便读取出来

注意数据库中的下标是从1开始的,而不是0

3 告诉数据库执行

statement.execute()

或者

statement.executeUpdate()

无非就是返回值不一样,但是都通知到数据库执行

需要注意的是,不要添加sql语句在execute等方法参数,这样会让数据库误以为是执行这个参数语句而不是执行preparedstatement语句

sql模版如何去写

常用的观察就这么几种

1 ?占位

除了使用?占位的方式之外还可以使用:name的方法进行

例如

select * from employee where id = :id

此时就可以通过

statement.setString(“id”, id);

来进行传参

但是这种需要保证参数名不重复,而且会损失一点性能

CallableStatement的工作

callableStatement实际上即继承自preparedStatement,而preparedStatement即继承自statement的

这个语句对象主要的功能就是call ,这个call就是关系型数据库中的调用存储过程的关键字

所以这个语句对象可以调用数据库的存储过程

1 获取语句对象

state = conn.prepareCall( sql_stirng )

这个调用存储过程的语句和标准的sql语句有一点点的不一样

我们需要在整个调用语句的外边使用大括号包裹

2 如果存储过程需要参数

还记得说callableStatement是继承自preparedstatement,所以前边的sql_sting实际上传入的就是sql模版字符串

我们需要传入的参数就和preparedStatement一样使用?进行占位

然后通过语句的set方法

setString(index,value)

setObject(index,value)

传入参数即可

3 执行后获取结果

state,executeQuery()

或者什么其他的执行,只要你知道这个存储过程返回的是一个int也可使用executeUpdate之类的

这个返回就是一个ResultSet

如果不确定是否是ResultSet也可以直接使用execute

返回向语句对象索要ResultSet

getResultSet();

有出参的存储过程

存储过程需要我们传入一个变量作为结果的承载返回

所以callable需要传入一个变量过去

callablestatement.registerOutParemeter(index,Types.Enum)

注册是为了之后的获取,ok这样注册之后在去执行

然后我们就可以获取这个值了

callablestatement.getInt(index)

callablestatement.getstring(index)

….

之类的

入参出参基于一身的参数

这种参数就是inout参数实际上就是注册出参的时候,index注册成我们传入参数的位置即可

多个结果表的处理

可能结果是多个结果表resultset

callablestatement提供一个方法

getMoreResults()

调用这个方法,就会切换到下一个结果集,如果有的话

调用自定义函数

如果不是存储过程而是自定义函数,不同的地方在于自定义函数一定会有一个返回值 在sql语句中写成是returns 变量

相比于存储过程,我们在sql模板上要这样去写

{? = call functionanem(?)}

这个时候我们注册出参,就要注册index = 1这个位置的

这样就可以了

结果集ResultSet的工作

ResultSet是用于向数据库查询结果集的对象

常用的方法就是next(),选中下一条数据

通过调用getXX(index) 方法来获得每一个单元格中的数据

可滚动结果集

实际上这个结果集是依附于statement存在的,利用的是statement的流程

在创建statement的时候可以传入参数,来设置此语句对象是否支持滚动,默认就是不支持滚动的,这样效率更高

1
2
3
4
conn.createStatement(ResultSet.Tyer_scroll_insentitive, ResultSet.Concur_read_only);
//表示可滚动,不敏感(也就是表示数据是查询那一刻的状态,而不是当前实时的状态),只读表示不能通过修改结果集反向修改数据库


滚动方法

next()和previous()控制游标上下移动

relative(int) 相对位移,1表示向下移动一行,2就是向下移动两个,-1表示向上移动一个,没有也是返回false

beforeFirst()光标回至标题栏

afterLast()光标置于最后一行的后面

first()光标放置第一行,如果没有第一行,返回false

last()放置最后一行,如果没有最后一行返回false

absolute(row_index) 光标置于给定行,没有这一行,返回false

getRow()返回光标所在行

更新方法

分为两步,第一步就是传入更新数据

第二步就是更新结果集执行

resultSet.updateXX(column_index,更新数据)

result.updateRow() 执行更新

事务操作

实际上在工作流程标签下的connection栏目,对于jdbc的事务操作有了基本的介绍

这里补充说明一点内容

1 事务保存点

rollback()操作实际上有一个参数

可以传入一个savePoint类型的保存点,再回滚的时候,如果保存点存在,就可以传入保存点位置,回滚就会回滚到保存点的位置

这个保存点的设置是这样的

savePoint = conn.savePoint();

2 DDL语句无法被事务限制

也就是当DDL语句定义语句,发送到数据库之后,数据库可不会管你这个是不是在一个事务里面,会直接立马执行,并且无法回滚,回滚了也无法影响执行后的结果

实际上是因为DDL事务操作代价太大,所以不支持

数据库连接池 – 基于JDBC

数据库连接池很好理解,就是一个缓冲,尽量减少建立连接,直接使用现有连接的技术

这个技术的关键就在于最低连接数,最高连接数,他俩之间的差值就是性能最好的缓冲区间,大于最大连接数不要再建立连接,小于最小连接数我们就闲置连接

目前最流行的数据库连接池技术(JDBC的)就是阿里开源的Druid和HikraiCP两种

连接池的一般抽象

使用连接池就意味着,我们将创建连接和管理连接的权限交给了连接池,所以自己不要去手动创建连接和关闭连接了,我们只需要去向连接池申请取得连接即可

连接池把向我们提供连接的对象 一般称作 数据源

数据源 DataSource

实际上数据源就是我们用户所看到的数据库连接池的样子,没有其他的对象了,这使得数据库连接池的使用非常简单易懂

使用数据源就像我们使用JDBC一样

1 创建数据源 就是创建了一个连接池

1
DruidDataSource datasource = new DruidDataSource();

2 配置建立连接的参数

1
2
3
4
5
dataSource.setDriverClassName("your driver path");
//因为直接加载这个驱动,实际上只是DriverManager得到了驱动对象,而dataSource并不打算使用DriverManager来获取连接,看来DataSource是自己去获得的driver来进行连接,看不起DriverManager
dataSource.setUrl("jdbc url");
dataSource.setUsername("username");
dataSource.setPassword("password");

3 配置数据源策略

1 设置初始连接数量 实际上没啥用

setInitialSize(int)

2 设置最小空闲连接数量 这样设置大一点爬升更流畅,但是日常开销会增加

setMinIdle(int)

3 最大同时激活连接

setMaxActive(int)

4 最常空闲时间 超过就销毁

setMaxWait( -1 表示无限等待)

实际上更多的是通过配置文件的形式对数据连接池的策略进行配置

HikariCP连接池

因为之前数据库的一般抽象连接池部分使用的是Druid连接池举例,所以这里就只介绍一下HiKraiCp连接池的具体使用

1 连接池的创建(数据源)

HikariDataSource datasource = new HikariDateSource();

2 配置驱动

setDriverClassName(“driver class path”)

3 配置策略

setMininumIdle(int)

setIdleTimeOut(int)超时时间实际上就是setMaxWait

数据库工具类 -DBUtils

这个Commons DBUtils是Apache组织开发的一个开源的JDBC的数据库工具类

主要的目的就是简化JDBC的开发过程

对于数据库表的查询之类的操作可以直接保存为集合,这样不用自己去一条一条的查

对于写操作写好sql直接传参即可,后面可以配合mybatis使用

可以使用数据源,数据库连接池技术,直连获取连接

主要架构

DBUtils主要的设计架构就是两个类

1 QueryRunner

这个类的主要功能就是

快速的将java类,集合等java元素填入sql模版字符串

常用方法:
execute(Connection, sql, params… )

update(conneciton, …)

query(connection, …)

如果配置数据源

在new QueryRunner的时候可以传入一个DateSource的接口

这样相当于就是配置了数据源,接下来使用这个QueryRunner就不需要传入connection连接了,QueryRunner会自动获取连接

2 ResultHandler接口

这个接口主要用来接受ResultSet的所有数据,将数据封装成java形式的对象

这个接口有不同的实现

BeanHandler(class<>) 封装成一个对象

BeanListHandler 一行一个对象

ArrayHandler() 第一行装入数组

ArrayListHandler() 每一行看成数组装入List

ScalarHandler(int columnIndex) | ScalarHandler(String columnName) 返回第一行某列的值

MapHandler实际上是BeanHandler的通用版,也就是直接将列名作为键,数据作为值,一行就是一个Map

MapListHandler

BeanHandler的原理

直接可以通过new BeanHandler(PoJO.Class)来创建一个BeanHandler

工作原理就是获取BeanHandler的所有set方法的名字,然后根据名字匹配我们ResultSet中的对应列名的数据

这一点就是和Spring的自动注入是一样的

MyBatis 持久层框架

在持久化层中,我们的sql字符串频频出现在java源代码中,不仅仅打乱了java格局,还使得sql语句耦合在源代码中,难以修改

Mybatis是一个持久化框架,目的就是将持久化代码与java源代码分离开来

实际上主要的学习内容就是mybatis的配置文件的语法

框架就是涵盖了所有的意思

持久层框架相当于就是涵盖了DBUtils,JDBC,这些东西

疑惑点

  1. mapper.xml如何与mapper类建立联系?

通过在mapper.xml中的namspace设置类的全路径名,以及在mybatis-config.xml中配置mapper.xml

也就是mybatis通过配置找到mapper.xml再从mapper.xml找到mapper类

  1. mapper.xml中的语句如何与mapper类中的方法建立映射?

MyBatis会根据方法名、参数类型、返回类型等信息定位并执行对应的SQL语句

主要的定位方案是就是方法名对应 语句标签中的id属性值

Mybatis的三层

基础支撑层 实际上就是JDBC这一块,管理连接,事务,以及加载配置文件的底层 缓存一些常用的sql结果

数据处理层 实际上就是DBUtils的功能,参数映射 sql解析 sql 执行 结果映射这些

接口层 实际上就是 ResultHandler,以及queryRunner这个功能包括数据查询,数据新增,更新接口获取配置

配置文件

都说了mybatis的学习主要就是配置文件的学习

mybatis有两种文件

  • config.xml全局配置文件 全局信息,数据库信息之类的,事务的配置,连接池的配置…

  • Mapper.xml映射文件 用来配置sql语句

config.xml mybatis的全局配置

这个文件中常用的配置信息就是

1 开发环境映射不同的配置信息

1
2
3
4
5
6
7
8
<enviroments deafult="development"> <!-- 注意这个default一定要注意写上,我没写上搞了半天 -->
<enviroment id="development">
</enviroment>
<enviroment id="production">
</enviroment>
<enviroment id="test">
</enviroment>
</enviroments>

这样不同的环境mybatis的配置就不一样了

具体那些配置需要不一样?

在enviroment标签里面,子标签有这些

1
2
3
4
5
6
7
8
9
10
<!-- 1 事务管理方式 值JDBC -->
<transactionManager type="JDBC"></transactionManager>
<!-- 2 数据源配置 值:POOLED连接池 UNPOOLEED直连 JNDI-->
<!-- 实际上type这里传的是类名,这个类必须继承自PooledDataSourceFactory,这个类自己里面有一个datasource,这个datasource就是和HikariCP和Druid他们new出来的一样 -->
<dataSource type="POOLED">
<property name="driver"value="具体的值"></property>
<property name="url"value="具体的值"></property>
<property name="username"value="具体的值"></property>
<property name="password"value="具体的值"></property>
</dataSource>
小技巧 引用别的配置文件

记得spring中引用properties文件还得使用context:property-placeholder 命名空间导入之后才能用

而mybatis直接可以通过<properies resource=”filepath”>来直接导入

然后使用类似EL表达式的形式就可以直接引用我们properties的值了

1
2
3
4
<property name="driver"value="${jdbc.driver}"></property>
<property name="url"value="${jdbc.url}"></property>
<property name="username"value="${jdbc.username}"></property>
<property name="password"value="${jdbc.password}"></property>

2 配置Mapper.xml文件扫描

这样我们的SqlSessionFactory才能够找到

1
2
3
<Mappers>
<mapper resource=" filepath"></mapper>
</Mappers>

Mapper.xml映射配置文件

配置文件的基本思想就是,我们配置一条sql模版,根据传参自动填入,我们的java代码只负责传参,不负责实现,参数的类型转换都不要负责

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

select元素

这个元素是我们最为常用的,也是最为复杂的,功能最为强大的

典型的格式

1
2
3
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>

sql语句的主体在标签内部

描述信息都在标签的属性中

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的 参数,默认值为未设置(unset)。
parameterMap 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
fetchSize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

mybatis的核心API

sqlsession相当于我们的statement

sqlsessionFactory相当于我们的connection

sqlsessionFactoryBuilder相当于我们的DriverManager,读取配置文件,建立连接,得到sqlsessionFactory

基本流程

1
2
3
4
5
6
SqlSessionFactoryBuilder builder = new ...();
Inputstream xmlFileInputstream = new FileInputStream("filepath");
//获取factory
SqlSessionFactory factory = builder.build(xmlFileInputstream);//读取配置文件
//获取sqlsession
SqlSession session = factory.openSession();

sqlsession的方法

select系列

Mybatis入门程序结构

环境配置

先创建maven工程

导入myabtis依赖,org.mybatis

项目编译管理,因为Mybatis需要将xml文件配置到pojo类相似目录下,所需要maven的pom.xml配置源文件内编译资源文件

1
2
3
4
5
6
7
8
9
10
11
12
<build>
<resources>
<resource>
<directory>/src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

Mybatis配置

mybatis需要两种配置文件

  1. 全局配置文件,这个配置文件是用来配置mybatis的初始化和全局的一些参数的
  2. Mapper映射文件,这个配置文件是用来mybatis解析调用的,里面写的是一些sql语句

在Idea中创建mybatis的配置文件的模板

mybatis的xml文件有一定的固定语句和格式,这些东西都可以使用一个模板代替生成

在菜单-设置-编辑器-文件和模板这样路径可以添加文件模板

image-20240121145411016

mybatis-config.xml 模板内容如下:

1
2
3
4
<?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>
</configuration>

mapper.xml模板内容如下:

1
2
3
4
<?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="">
</mapper>

mybatis-config.xml配置内容

1 配置environments
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<environments deafult="此处必填">
<environment id="环境名字">
<transactionMapperManager type=""></transactionMapperManager>
<datasource type=""></datasource>
</environment>
<environment id="环境名字">
<transactionMapperManager type=""></transactionMapperManager>
<datasource type=""></datasource>
</environment>
<environment id="环境名字">
<transactionMapperManager type=""></transactionMapperManager>
<datasource type=""></datasource>
</environment>
</environments>

不同的开发环境,mybatis在启动的时候会读取不同的数据源和事务处理

这样就不需要做多个配置文件,进行手工切换了

常用的环境配置如下

1
2
3
4
5
6
7
8
9
10
11
12
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<!-- datasource的type属性实际上是根据这个值,寻找的一个类 做的自动注入,这个类必须要是mybatis认识的PooledDataSourceFactory,所以自己的数据库连接池一定要继承自这个类 -->
<!-- 还可以取值UNPOOLED 不使用连接池 JNDI -->
<datasource type="POOLED">
<property name="driver" value=""></property>
<property name="url" value=""></property>
<property name="username" value=""></property>
<property name="password" value=""></property>
<!-- 实际上和 spring的配置文件差不多,通过set注入值,不过这里的是调用的对象中的dataSource成员变量的set方法 -->
</datasource>
</environment>

PooledDataSourceFactory类

这个类真正的数据源是类中一个名为dateSource的变量

所以我们只需要覆盖这个变量即可替换连接池数据源

技巧: 引入properties配置文件内容

1
2
3
4
5
<!-- 通过标签导入 -->
<properties resource="db.properties"></properties>
...
<property name="driver" value="${jdbc.driver}"></property>
<!-- 类似EL表达式的引用方法 -->
2 配置mybatis日志
1
2
3
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"></setting>
</settings>

这个配置相当于将日志自动输出到控制台(std out)标准输出流

mapper.xml配置内容

这个配置文件就是用来编写sql语句的

在<mapper>标签子标签就是一些增删改查的标签,namespace就是一个子标签id的前缀

例如

1
2
3
<select id="sql name" resultType="类全路径名">
select * from student
</select>

resultType就是用来处理JDBC查询到返回来的ResultMap的,如果是使用seletcList方法调用这个一条sql,那么就算只有一条数据或者没有数据也会返回一个list给你,反之如果是使用select方法调用,那么即使有很多条数据也只会将第一条数据封装好返回

当配置好mapper.xml之后,需要让mybatis-config.xml找到你

1
2
3
4
<!-- 在mybatis-config.xml中 -->
<mappers>
<mapper resource="your mapper,xml"></mapper>
</mappers>

关于select,insert,update,delete标签的本质

实际上这些标签的本质只是为程序标注这个一个sql语句而已,这些标签本质上就是一个标签,不过写成不同的名字对于程序的可读性有提升

决定究竟是返回哪种类型,执行那种类型的语句的代码是:
sqlsession.select(?) 还是 session.insert() 关键在于调用的是哪种方法

如果调用的insert方法,那么返回值只能是int,并且在执行这条语句之前,mybatis会自动开启事务,因为insert语句会可能修改数据库中的数据,而如果使用select那么mybatis就不会开启事务,那么发送给数据库语句会被立马执行,不需要提交操作

使用mybatis进行数据库操作

1
2
3
4
5
6
7
8
9
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();//这个对象相当于是DriverManager的作用,是用来读取配置文件的
InputStream in = Resources.getResourceAsStream("your mybatis-config file path");
//注意这个Resources是org.apache.ibatis.io.Resources这个类
SqlSessionFactory factory = builder.build(in);//这个factory对象的作用就是用来获取连接的
//通过factory.openSession()获取连接
SqlSession session = factory.openSession();
//session实际上是连接,而不是statement,他会默认使用preparedStatement进行通信
session.selectList("selectAll");//传入的是mapper中的一个select的id值
session.close();

mybatis单表操作

无传参的调用sql操作

先编写mapper.xml

1
2
3
<insert id="insert">
insert into student values(default,"some name","some gender","some age");
</insert>

使用mybatis调用insert方法执行这个sql语句

1
2
sqlsession.insert("insert");
sqlsession.close();

发现执行后没有效果,点击进入openSession()方法

OpenSession方法的默认值

实际上opensession方法获得一个类似连接connection的对象sqlSession

这个sqlSession中所表示的connection默认autoCommit=false

记得我们使用jdbc的时候getConnection()默认是自动提交的事务,这里不一样

所以需要添加手动提交事务

session.commit();

如果执行的是select等等对于数据库数据没有影响的操作,就不会按照事务管理,不用手动提交了,数据库会直接运行,所以就不需要这个提交操作

有传参的调用sql操作

当我们需要动态生成sql,比如传入一些参数来组成sql语句,类似于preparedstatement中sql模版那种

mybatis在mapper.xml中进行配置提供了相应的语法

1
2
3
<insert id="insertObject" parameterType="类的全路径名" >
insert into student values(null, #{name}, #{age}, #{gender})
</insert>
  1. 通过#{}这样的语法调用传入的对象的get系列方法获得值,通过preparedstatement将参数后续传递给服务器

另一方面我们在调用的时候要传递一个对象过去

1
2
3
4
session.insert("insertObject", student);
//所有的调用sql的方法都有两种,一种是直接调用,传入一个string来查找sql的id的
//一种是还要传入一个object参数的
session.commit();//如果设置了autocommit=true就不需要手动提交了

insert方法.update方法,delete方法的本质

实际上这三个方法都是一样的处理,被写成不同的方法只是为了提高可读性

他们和select方法不一样,实际上这三个方法可以被总结道update就是修改

select方法单独是一种,对于select方法的返回值有特殊的处理,而update系列方返回值都是一个int

  1. 通过${}形式作为占位符

这种方式就是让mybatis直接获取到object之后,替换占位符,相当于本地拼接好sql语句在发送给数据库,很可能出sql注入危险

更新而且查询操作

前边说的,我们的udpate()方法返回的是一个int类型,实际上可以通过修改传入参数的的值进行

在配置sql语句中这样写

1
2
3
4
5
6
7
<insert id="insertAndGetId" parameterType=" class path name">
<selectKey keyProperty="id" keycolumn="id" resultType="int" order="after">
select last_insert_id()
</selectKey>
<!-- keyProperty指的是在这个参数类中的set名字,mybatis会将返回值注入到传入对象中,keycolumn指的是在数据库中的字段名字,resulttype值得是返回的类型,order指的是先插入还是先查询 -->
isnert into student value(null, #{name}, #{age}, #{gender})
</insert>

删除操作

1
2
3
<delete id="delete" parameterType="int">
delete from student where id = #{id};
</delete>

模糊查询

一种是使用${}

1
delete from student where name like '${name}%'

一种是使用#{}

1
delete from student where name like #{name}"%"

更新操作

实际上就是老一套

查询操作

如果想要得到所有的数据,使用selectList()方法

如果只想要得到第一条数据,就可以使用select()方法

返回结果的封装方法

很多情况下数据库中的字段和我们程序中的实体类中的字段并不一定相同,在这种情况下保证字段封装的准确性

1
2
3
4
5
6
7
8
9
10
<select id="selectList" parameterType="student" resultType="student">
select * from student where name like "%"#{name}"%"
</select>
<!-- resultMap是用来配置结果匹配的,因为如果集成在 语句标签里会很臃肿,所以语句标签知识引用 -->
<resultMap id="userResultMap" type="com.example.domain.User">
<id property="id" column="user_id" />
<result property="username" column="username" />
<result property="email" column="email" />
<!-- 定义其他属性的映射 -->
</resultMap>

Mybatis的DAO层设计

传统的DAO设计方式

1
2
3
4
5
6
7
8
9
10
//先写一个dao接口
public interface StudentDao{
default SqlSession getSqlSession(){
return SqlSessionFactoryBuilder.build(ResourceAsStream("mybatis-config.xml")).openSession();
}

List<Sutdent> selectList();

int insert(Student stu);
}

再写一个impl类使用sqlsession操作数据库即可

mybatis的mapper设计方式

mybatis自动生成实现类的一种技术

程序员只需要写DAO接口,mybatis会根据接口自动生成实现类,但是肯定是需要一些信息,mybatis才能正确生成实现类

mybatis需要的信息

  1. mapper.xml配置文件

还记得mapper.xml吗,有一个namespace属性在mapper标签之中

如果想要为一个接口生成实现类,就必须在namespace这一块填上接口的全路径

这样mybatis就能知道接口要实现的方法都是什么

  1. 接口中的方法的签名必须和mapper.xml中的语句的id,parameterType,resultType相同,这样mybatis就可以确认生成的方法和接口匹配

得到实现类对象

mybatis动态生成的类,我们自己是获取不到的,要像mybatis要

1
接口 = sqlsession.getMapper(接口.class);

这个就像是spring中的getBean方法一样,实际上是一个单例对象

实际上是通过动态代理生成的代理对象

mybatis配置文件简化写法

在写配置文件的时候,经常写类的全路径名,非常麻烦

mybatis提供了一些解决配置重复的问题

别名配置

可以为类起一个简短的别名,进行引用,来代替原来的全路径类名

1
2
3
4
5
<!-- 注意这个别名配置是在 mybatis-confi.xml中写的,所有的mapper都能使用 -->
<typeAliases>
<typeAlias type="全路径" alias="别名"></typeAlias>
<!-- 别名不区分大小写,所以String可以写成string -->
</typeAliases>

或者这样写

1
2
3
4
<typeAliases>
<package name="包名"></package>
<!-- 配置了package 那么所有的出现的别名就是会去到这个包下找同名类 -->
</typeAliases>

使用注解方式简化mybatis的配置

mybatis配置的mapper.xml文件实际上sql语句并不直观,也并不好理解,在开发的时候往往是先有xml再有pojo和dao这样,实际上并不利于连续的开发

想要专注在java的开发,使用注解可以减轻xml的配置打乱开发流程

注解运行的方式

注解依赖于Mapper接口存在,依赖于具体的dao方法存在,mybatis读取到接口配置了mapper注解,就会从注解中解析出sql信息从而生成mapper实现类

1
2
3
4
public interface studentMapper{
@Insert("insert into student values(null, #{name}, #{age}, #{gender})")
int insert(Student stu);
}

所以注解的形式就是利用了mapper,自动生成mapper实现类

注解配置的优点

不用配置mapper.xml,依赖mapper接口存在

缺点

sql信息耦合在源代码中,无法修改

动态拼接sql

什么是动态拼接sql?

例如更新一个student信息

student有name,age,gender,score四个属性

此时传入了一个更新的student的对象,如何写更新sql?

最好的方式就是对象中所有不为空的字段我们都进行更新,这样就不确定到底有哪些字段会进行更新

当然你也可以把每种组合都写一个sql语句,java代码判断走哪一个sql,但是这样就很冗余痛苦

动态拼接sql就是让程序自己根据传入参数写一个sql语句

mapper.xml中的语法

mybatis优先一步取得传入参数,再根据传入参数运行sql的配置语句,最后得到一个生成的sql语句,大概就是这么个逻辑

关键就是在sql语句中做手脚

1
2
3
4
5
6
7
8
9
<select id="selectCondition" parameterType="student" resultType="student">
select * from where 1=1
<if test="name!=null and name!=''">
and name like "%"#{name}"%"
</if>
<if test="age!=null and age!=''">
and age = #{age}
</if>
</select>

这个语法就像是jstl一样

实际上就是默认字符都是sql语句,所以用标签包裹我们的mybatis逻辑判断语句

常用的动态拼接sql标签

最常用的if标签,以及choose-when-otherwise标签,逻辑控制标签

1
2
3
4
<!-- if test="判断语句" -->
<if test="name!=null and name != ''">
some sql statement
</if>
1
2
3
4
5
6
<!-- choose-when标签 用来简化多级if -->
<choose >
<when test="some logic code"> some sql statement</when>
<otherwise> some sql statement</otherwise>
</choose>
<!-- 匹配一个when标签后自动退出choose标签,所以不会重复匹配多个 when标签 -->

where标签,mybatis自动判断where条件是否存在

1
2
3
4
5
6
select * from student
<where>
<if test="name!=null and name!=''">
some where sql code.
</if>
</where>

这样如果where条件不存在就不会添加where …,并且如果内容开头是and 等不符合规范的字符会自动删除前缀

如果where条件存在就会添加上where + 后面的sql statement

trim标签,这个标签的意思就是裁剪的意思

1
2
3
4
5
6
7
select * from student
<trim prefix="where" prefixOverrides="and" suffixOverrrides="and" suffix=" order by age">
<if test="name!=null">
and name = #{name}
</if>
some sql statement.
</trim>

prefix属性作用就是如果不为空,就会添加prefix’前缀

prefixOverrides属性的作用就是匹配到内容如果是以 这个开头的,直接剪除

suffixOverrides属性的作用就是匹配到内容如果以 这个作为结尾,直接剪除

suffixOverrides属性就是如果内容不为空,就会添加上一个后缀

注意前后的空格是默认剪除的,所以在这个例子中,空格加上and和直接以and开头都会被剪除

set标签 ,这个标签和where标签异曲同工

1
2
3
4
5
6
7
update student
<set>
<if test="name!=null"> name = #{name},</if>
<if test="age!=null"> age = #{age},</if>
<if test="gender!=null"> gender = #{gender}</if>
</set>
where id=#{id}

容易出现的问题就是,后面多一个逗号

可以通过trim标签实现更为灵活的功能,代替set标签

foreach 标签 循环,遍历,重复功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
delete from student
<where>
1=1
<!-- 数组和集合都可以foreach -->
<foreach collection="array" item="id" open="相当于就是trim中的prefix前缀" close="这个就相当于是后缀" separator="自动添加,删除最后一个,这样就是分隔符">
#{id}
</foreach>
</where>
<!-- 下面是一个例子 -->
delete from student
<where>
<foreach collection="array" item="id" open="id in (" close=")" separatoe=",">
#{id}
</foreach>
</where>

如果传入的参数是数组或者list,那么会直接匹配到collection,如果是一个对象,那么才会根据这里的名字寻找set方法获取属性值

这种拼接sql对于大量数据不适合,如果有大量的插入,那么这个sql模版会很长,数据库需要等待的参数会很多,数据库必须留出位置来接受这些参数,所以效率很低

对于大量的数据操作,最好使用Batch操作的Executor,告诉数据库这个是大量数据操作

bind标签 用途就是对于传入的参数进行最后的修改

1
2
3
4
5
6
7
<bind name="newName" value="'%'+name+'%'"></bind>
select * from student
<where>
<if test="name!=null">
name like #{newName}
</if>
</where>

注解中使用动态拼接

1
2
3
4
5
6
7
@Select("<script> select * from student <where> <if test=\" name!=null\"> name like \"%\"name"" </if></where></script>")
List<student> selectList();
//可以使用{}包裹<script>字符串,使用逗号分割每一行,myabtis读取的时候会自动拼成一个字符串
@Select({"<script> select * from student
<where> <if test=\" name!=null\">
name like \"%\"name""
"})

Sql片 重用sql语句

有些sql语句的部分非常常用,而且很长一串,我们就可以提取出来,作为一个独立的部分供别的语句引用

1
2
3
4
5
6
7
8
9
 <!-- 定义sql片 -->
<sql id="sql name">
some sql statement
</sql>
<select id="selectAll" resultType="student">
select * from student
<!-- 导入sql片 -->
<include refid="sql name"></include>
</select>

sql片中当然可以用其他的动态拼接标签


持久层 再学习
https://wainyz.online/wainyz/2024/01/15/持久层 再学习/
作者
wainyz
发布于
2024年1月15日
许可协议