0%

  • 需要先安装JDK

下载

  • Zookeeper官网:
1
https://zookeeper.apache.org/

img

  • 下载页面:
1
https://zookeeper.apache.org/releases.html

img

  • 选择zookeeper-3.5.7版本下载,

img

1
$ wget https://archive.apache.org/dist/zookeeper/zookeeper-3.5.7/apache-zookeeper-3.5.7-bin.tar.gz

解压、移动

1
2
3
4
5
# 解压
$ tar xzvf apache-zookeeper-3.5.7-bin.tar.gz
# 移动
$ sudo mkdir -p /opt/modules
$ sudo cp -r apache-zookeeper-3.5.7-bin /opt/modules/apache-zookeeper

配置快照目录

  • 创建存储快照的目录,
1
$ sudo mkdir -p /opt/modules/apache-zookeeper/zkData
  • 复制并修改默认配置文件,
1
2
3
$ cd conf/
$ sudo cp zoo_sample.cfg zoo.cfg
$ sudo vim zoo.cfg
  • 修改dataDir,
1
dataDir=/opt/module/zookeeper-3.5.7/zkData
  • 修改zkEnv.sh,

img

添加JAVA_HOME路径配置

运行

  • 开启Zookeeper服务器,
1
2
$ cd ../bin/
$ sudo bash zkServer.sh start

img

  • 查看Zookeeper服务器状态,
1
$ sudo bash zkServer.sh status

img

  • 启动Zookeeper客户端,
1
$ sudo bash zkCli.sh

img

此时可以输入一些命令,

img

  • 退出Zookeeper客户端,
1
[zk: localhost:2181(CONNECTED) 5] quit

img

  • 关闭Zookeeper服务,
1
$ sudo bash zkServer.sh stop

img

配置解读

配置项 作用
tickTime 心跳时间
initLimit Follower与Leader建立连接的超时时间,单位是Tick
syncLimit Follower和Leader之间数据延迟的最大时间长度,单位是Tick
dataDir ZooKeeper的数据目录,主要目的是存储内存数据库序列化后的快照路径。如果没有配置事务日志(即dataLogDir配置项)的路径,那么ZooKeeper的事务日志也存放在数据目录中
clientPort 服务器给客户端连接的端口号

VC++ 项目导入SFML多媒体库

1 下载、解压

1.1 下载

  • 从以下网址下载 SFML 源文件压缩包,
1
https://www.sfml-dev.org/download/sfml/2.5.1/

img

  • 根据开发环境的编译器选取对应的源码压缩包下载

  • 操作系统的位数是根据目标平台的配置选取的。32位版本编译出的程序就可以在x86和amd64平台运行;64位版本编译出的程序只能在amd64平台运行

1.2 解压

  • 将源码压缩包解压到任意文件夹下,

img

2 导入

2.1 拷贝文件

  • 这是一个空项目的目录结构,

img

  • 创建 includelib 目录,

img

  • 将 SFML 头文件放到项目的 include 目录里面,

img

  • 将 SFML 库文件放到 lib 目录里面,

img

  • 将 SFML 动态链接库文件复制到项目根文件夹下,

img

2.2 配置附加包含目录

  • 打开项目属性页,

img

img

  • 修改目标配置和平台,

img

  • 跳转到配置属性 -> C/C++ -> 常规,

img

  • 添加附加包含目录,

img

2.3 配置附加库目录

  • 跳转到配置属性 -> 链接器 -> 常规,

img

  • 添加附加库目录,

img

2.4 配置依赖库文件

  • 跳转到配置属性 -> 链接器 -> 输入,

img

  • 修改目标配置为 Debug,

img

  • 添加附加依赖项,

img

  • 修改目标配置为 Release,

img

  • 添加附加依赖项,

img

3 测试

  • 使用以下代码替换 main.cpp 文件的所有内容,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <SFML/Graphics.hpp>int main()
{
sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
sf::CircleShape shape(100.f);
shape.setFillColor(sf::Color::Green);

while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}

window.clear();
window.draw(shape);
window.display();
}

return 0;
}
  • CRTL + F5 查看效果,

img

如果弹出一个窗口,并且内部有一个绿色的圆,就说明之前的配置没有问题

MyBatis缓存

1 一级缓存(本地缓存)

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
  • 一级缓存是sqlSession级别的缓存,是一直开启的

1.1 一级缓存失效的四种情况

  • sqlSession不同:一级缓存是sqlSession级别的缓存,所以如果重新开一个sqlSession,并不会共享其他sqlSession的缓存,仍然需要发送请求到数据库进行查询
  • sqlSession相同,查询条件不同:这种情况很好理解,因为之前没有查询过,所以在一级缓存中没有对应的数据
  • sqlSession相同,两次查询间执行了增删改操作:MyBatis执行增删改操作会清空缓存
  • sqlSession相同,手动清空了一级缓存:调用SqlSession对象的clearCache方法清空一级缓存

2 二级缓存(全局缓存)

img

  • 二级缓存是namespace级别的缓存

2.1 工作机制

  • 一个session过程中查询的数据会被放在当前session的一级缓存中
  • 如果session关闭,一级缓存中的数据就被保存到二级缓存中,从而新的session查询就可以参照二级缓存
  • 不同namespace查出的数据会被放在自己对应的缓存中

2.2 使用

  • 开启全局二级缓存配置,
1
<setting name="cacheEnabled" value="true"/>
  • 在Mapper xml文件配置使用二级缓存,
1
2
3
4
<mapper namespace="com.lnhoo.mapper.EmployeeMapper">
<cache eviction="LRU" readOnly="true" size="65536"></cache>
......
</mapper>
  • 实现POJO的序列化接口

2.3 cache标签参数

参数 功能
eviction 缓存的回收策略, LRU:最近最少使用的,移除最长时间不被使用的对象 FIFO:先进先出,按对象进入缓存的顺序移除 SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象 WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则地对象
flushInterval 缓存刷新间隔,即每隔多少毫秒清空缓存,默认不清空
readOnly 是否只读, 只读:MyBatis认为所有访问缓存的操作都是只读操作,不会修改数据;为了加快获取速度,直接将数据在缓存中的引用返回给用户;不安全,但是速度快 读写,MyBatis认为获取的数据可能会被修改,会利用序列化、反序列化技术克隆一份新的数据;安全,但是速度慢
size 缓存的元素个数
type 自定义的缓存实现类(通过实现MyBatis Cache接口)

2.4 和缓存有关的设置、属性

  • 1)cacheEnabled:决定二级缓存是否启用
  • 2)useCache:每个select标签都有一个“useCache”属性,决定是否缓存当前sql查询到的数据,默认为true;useCache决定的是二级缓存,对一级缓存无影响
  • 3)flushCache:每个增删改标签都有一个“flushCache”属性,决定是否在sql执行后清空一级、二级缓存
  • 4)sqlSession.clearCache():只清除当前session的一级缓存
  • 5)localCacheScope:本地缓存作用域,默认值为“SESSION”,可选值为“STATEMENT”

3 MyBatis整合ehcache

img

3.1 导入适配器包

1
2
3
4
5
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.2</version>
</dependency>

3.2 导入第三方缓存包

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
<!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.8.0-beta4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.8.0-beta4</version>
<scope>test</scope>
</dependency>

3.2 Mapper xml配置使用自定义缓存

1
2
3
4
<mapper namespace="com.lnhoo.mapper.EmployeeMapper">
<cache eviction="LRU" readOnly="true" type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
............
</mapper>
  • 可以通过cache标签的type属性指定自定义的缓存实现类

3.3 引用cache配置

  • 有时候希望能够重用其他namespace的cache配置,可以使用cache-ref标签,
1
<cache-ref namespace="com.lnhoo.mapper.EmployeeMapper"/>

MyBatis动态SQL

  • MyBatis的动态SQL通过if、choose、when、otherwise、trim、where、set、foreach等标签,可组合成非常灵活的SQL语句,从而在提高SQL语句准确性的同时,大大提高开发人员的效率

1 if判断 & OGNL

  • 可在Mapper xml配置文件使用if标签根据接口参数动态确定最终执行的SQL语句,
1
2
3
4
5
6
7
8
9
<select id="getEmployeeByCond" resultType="com.lnhoo.dao.Employee">
select * from employees where
<if test="id!=null">
id=#{id}
</if>
<if test="lastName!=null and lastName!=''">
and last_name like #{lastName}
</if>
</select>

2 where

  • 上面if标签的用法,如果第一个test不通过,后续执行的sql语句where后面就是and,不符合sql语法导致报错
  • 比较简单粗暴的解决方式是直接在where后面加个1=1,
1
2
3
4
5
6
7
8
9
<select id="getEmployeeByCond" resultType="com.lnhoo.dao.Employee">
select * from employees where 1=1
<if test="id!=null">
and id=#{id}
</if>
<if test="lastName!=null and lastName!=''">
and last_name like #{lastName}
</if>
</select>
  • 也可以使用where标签将所有查询条件包括在内,自动将多出来的and和or去掉,
1
2
3
4
5
6
7
8
9
10
11
<select id="getEmployeeByCond" resultType="com.lnhoo.dao.Employee">
select * from employees
<where>
<if test="id!=null">
id=#{id}
</if>
<if test="lastName!=null and lastName!=''">
and last_name like #{lastName}
</if>
</where>
</select>
  • 使用where标签的时候,and应该放在条件语句之前,比如这里“and”就放在“last_name like #{lastName}”之前,如果放在后面拼接出的sql就不符合语法规则

3 trim自定义字符串前后缀

  • 可以使用trim标签自定义字符串的前后缀,
1
2
3
4
5
6
7
8
9
10
11
<select id="getEmployeeByCondTrim" resultType="com.lnhoo.dao.Employee">
select * from employees
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null and lastName!=''">
last_name like #{lastName} and
</if>
</trim>
</select>

在这个例子中,trim标签设置所有if条件的前缀是“where”,去掉if条件末尾的“and”

  • trim标签的属性包括,
属性 作用
prefix 给拼装后的字符串加一个前缀
prefixOverrides 去掉整个字符串前面的字符
suffix 给拼装后的字符串加一个后缀
suffixOverrides 去掉整个字符串后面的字符

4 choos分支选择

  • choose标签可以实现类似Java中switch-case结构的功能,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="getEmployeeByAnyCond" resultType="com.lnhoo.dao.Employee">
select * from employees
<where>
<choose>
<when test="id!=null">
id=#{id}
</when>
<when test="lastName!=null">
last_name like #{lastName}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>

类比Java的switch-case结构,choose标签相当于switch关键字,表示开始一个分支选择;when标签相当于case关键字,表示其中的一个分支;otherwise相当于default,表示其余分支条件都不成立时的默认操作

  • choose标签中的各个分支是自上而下做判断的,只要其中一个条件满足,就执行操作,然后退出分支选择

5 set与if结合的动态更新

  • set标签和if标签结合可以实现动态更新的效果,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<update id="updateById">
update employees
<set>
<if test="id!=null">
id=#{id},
</if>
<if test="lastName!=null">
last_name=#{lastName},
</if>
<if test="gender!=null">
gender=#{gender},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="deptId!=null">
dept_id=#{deptId}
</if>
</set>
where id=#{id};
</update>

只有当传递了值的时候才会更新数据库表中对应得字段

  • set标签会自动处理出现在末尾多余的逗号
  • 同样的功能也可以用trim实现,此时prefix=”set”,suffixOverrides=”,”

6 foreach遍历集合

6.1 拼接条件

  • 有时候需要根据传递给接口的参数拼接出条件,
1
2
3
4
5
6
<select id="getEmployeeForEach" resultType="com.lnhoo.dao.Employee">
select * from employees where id in
<foreach collection="ids" item="eid" open="(" close=")" separator=",">
#{eid}
</foreach>
</select>
  • 接口声明中用@Param注解集合参数,
1
List<Employee> getEmployeeForEach(@Param("ids") List<Integer> ids);

比如上面的配置,“in”关键字后面跟的应该是“在一对括号内部用逗号间隔的数字”,用到foreach标签的属性作用分别是,

属性 作用
collection 要遍历集合的名称,使用@Param注解指定
index 遍历List的时候是索引; 遍历Map的时候是entry的key
item 遍历List的时候是取出的元素; 遍历Map的时候是entry的value
open 拼接字符串结果的前缀
close 拼接字符串结果的后缀
separator 每个元素之间的分隔符

6.2 MySQL批量插入

  • 在有些场合需要定义1个方法实现批量插入记录,
1
2
3
4
5
6
7
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(null, "rita", "0", "rita@gmail.com", 2));
employees.add(new Employee(null, "michael", "1", "michael@gmail.com", 1));
Integer cols = mapper.batchAddEmployees(employees);
session.commit();
System.out.println("inserted cols: " + cols);
  • foreach标签也可以实现批量插入的功能,
1
2
3
4
5
6
<insert id="batchAddEmployees">
insert into employees (last_name, gender, email, dept_id) values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName}, #{emp.gender}, #{emp.email}, #{emp.deptId})
</foreach>
</insert>

使用“.”操作符取出item内部的属性值

  • 也可以把foreach标签放在整个insert语句的外部,
1
2
3
4
5
6
<insert id="batchAddEmployees">
<foreach collection="emps" item="emp" separator=";">
insert into employees (last_name, gender, email, dept_id)
values (#{emp.lastName}, #{emp.gender}, #{emp.email}, #{emp.deptId})
</foreach>
</insert>

但是,此时需要在JDBC连接的时候配置allowMultiQueries选项为true,这样才能一次执行分号间隔的多条sql语句,

1
2
3
4
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true
jdbc.username=root
jdbc.password=******

7 内置参数

7.1 _parameter

  • _parameter代表整个参数:

(1)单个参数:_parameter就是这个参数

(2)多个参数:参数会被封装成一个map,_parameter就是代表这个map

7.2 _databaseId

  • _databaseId代表当前数据库的别名,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="getEmployeeById" resultType="com.lnhoo.dao.Employee">
<if test="_databaseId=='mysql'">
select * from employees
<if test="_parameter!=null">
where id=#{id}
</if>
</if>
<if test="_databaseId=='oracle'">
select * from tbl_employee
<if test="_parameter!=null">
where eid=#{id}
</if>
</if>
</select>

前提条件是需要在MyBatis全局文件中配置数据库别名,

1
2
3
4
5
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>

8 bind

  • bind可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量,
1
2
3
4
<select id="getEmployeeByName" resultType="com.lnhoo.dao.Employee">
<bind name="_lastName" value="'%' + lastName + '%'"/>
select * from employees where last_name like #{_lastName}
</select>

模糊查询无法通过“#”操作符取参数值,并且如果使用“$”操作符取参数值会有sql注入的风险

  • bind标签可以将OGNL表达式的值绑定到1个变量中,后续可以用”#”操作符引用

9 抽取可重用的sql片段

  • 抽取可重用的sql片段,方便后续引用,
1
2
3
4
5
6
7
8
9
10
<select id="getEmployeeByName" resultType="com.lnhoo.dao.Employee">
<bind name="_lastName" value="'%' + lastName + '%'"/>
select
<include refid="empCols"></include>
from employees where last_name like #{_lastName}
</select>

<sql id="empCols">
id, last_name, gender, email, dept_id
</sql>

使用include标签引用之前抽取的sql片段

  • sql标签可以引用include标签内定义的属性,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="getEmployeeByName" resultType="com.lnhoo.dao.Employee">
<bind name="_lastName" value="'%' + lastName + '%'"/>
select
<include refid="empCols">
<property name="_lastName" value="#{_lastName}"/>
</include>
from employees where <include refid="nameCond"></include>
</select>

<sql id="empCols">
id, last_name, gender, email, dept_id
</sql>

<sql id="nameCond">
last_name like #{_lastName}
</sql>

比如在这个例子中,nameCond sql标签就引用了include标签使用property定义的属性_lastName

MyBatis映射文件

1 增删改查概述

1.1 Mapper接口定义

  • 在Mapper接口中定义增删改查方法的参数和返回值类型,
1
2
3
4
5
6
public interface EmployeeMapper {
Employee getEmployeeById(Integer id);
void addEmployee(Employee employee);
void updateEmployee(Employee employee);
void deleteEmployeeById(Integer id);
}

1.2 Mapper配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<mapper namespace="com.lnhoo.dao.EmployeeMapper">
<select id="getEmployeeById" resultType="com.lnhoo.Employee">
select * from employees where id = #{id}
</select>

<insert id="addEmployee" parameterType="com.lnhoo.Employee">
insert into employees (id, last_name, gender, email) values (#{id}, #{lastName}, #{gender}, #{email})
</insert>

<update id="updateEmployee" parameterType="com.lnhoo.Employee">
update employees set last_name=#{lastName}, gender=#{gender}, email=#{email} where id=#{id}
</update>

<delete id="deleteEmployeeById" parameterType="int">
delete from employees where id=#{id}
</delete>
</mapper>
  • 在Mapper配置文件中将接口中的方法和sql语句绑定
  • mapper标签的namespace属性填上接口的全类名
  • 增(insert)、删(delete)、改(update)、查(select)标签的id属性填上接口中的方法名
  • 参数提供的字段、sql语句执行的结果以“#{名字}”的形式捕获

1.3 执行sql

1
2
3
4
5
6
7
8
9
10
@Test
public void testModify() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee jack = new Employee(0, "jack", '1', "xxx@163.com");
employeeMapper.addEmployee(jack);
session.commit();
}
}
  • 先获取到SqlSessionFactory对象,然后调用openSession方法开启一次会话,从会话中获取MyBatis自动创建的动态代理对象,之后就可以调用事先在EmployeeMapper接口中定义的方法实现对数据库的增删改查
  • 调用openSession方法如果不提供参数,此时的会话将不自动提交更改,需要在合适的时候手动调用commit方法提交更改;如果调用重载方法,则可以提供一个布尔类型的参数,表示是否自动commit

1.4 获取增、删、改执行结果

  • MyBatis支持将接口中增、删、改方法的返回值类型定义成void、Integer、Long、Boolean,
1
2
3
4
5
6
public interface EmployeeMapper {
Employee getEmployeeById(Integer id);
Integer addEmployee(Employee employee);
Long updateEmployee(Employee employee);
Boolean deleteEmployeeById(Integer id);
}
  • 返回值类型为整数时,表示sql语句执行影响的记录数
  • 如果返回值类型是布尔值,表示sql语句执行是否对数据库表内容造成影响

1.5 获取自增主键的值

  • useGeneratedKeys=”true”:使用自增主键获取主键值的策略
  • keyProperty:指定对应的主键属性,也就是MyBatis获取到主键值后,将这个值封装给Java Bean的哪个属性
1
2
3
<insert id="addEmployee" parameterType="com.lnhoo.Employee" useGeneratedKeys="true" keyProperty="id">
insert into employees (id, last_name, gender, email) values (#{id}, #{lastName}, #{gender}, #{email})
</insert>

2 参数处理

2.1 单个参数

  • MyBatis不作特殊处理,直接#{参数名}取出参数值
1
2
3
4
Employee getEmployeeById(Integer id);
<select id="getEmployeeById" resultType="com.lnhoo.Employee">
select * from employees where id = #{id}
</select>

2.2 多个参数

2.2.1 通过索引取出参数值

  • 多个参数会被封装成一个Map,索引为:param1、param2、…、paramN,
1
2
3
4
Employee getEmployeeByIdAndLastName(Integer id, String lastName);
<select id="getEmployeeByIdAndLastName" resultType="com.lnhoo.Employee">
select * from employees where id=#{param1} and last_name=#{param2}
</select>

2.2.2 命名参数

  • 使用@Param注解为参数命名,
1
2
3
4
Employee getEmployeeByIdAndLastName(@Param(value = "id") Integer id, @Param(value = "lastName") String lastName);
<select id="getEmployeeByIdAndLastName" resultType="com.lnhoo.Employee">
select * from employees where id=#{id} and last_name=#{lastName}
</select>

2.2.3 POJO

  • 如果多个参数正好是业务逻辑的数据模型,就可以直接传入POJO,#{属性名}取出传入的POJO的属性值,
1
2
3
4
Integer addEmployee(Employee employee);
<insert id="addEmployee" parameterType="com.lnhoo.Employee">
insert into employees (id, last_name, gender, email) values (#{id}, #{lastName}, #{gender}, #{email})
</insert>

2.2.4 Map

  • 如果多个参数不是业务逻辑的数据模型,没有对应的POJO,也可以传入Map,#{key}取出参数值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Employee getEmployeeByMap(Map<String, Object> map);
<select id="getEmployeeByMap" resultType="com.lnhoo.Employee">
select * from employees where id=#{id} and last_name=#{lastName};
</select>
@Test
public void testSelect() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Map queryMap = new HashMap<String, Object>();
queryMap.put("id", 1);
queryMap.put("lastName", "tom");
Employee employee = employeeMapper.getEmployeeByMap(queryMap);
System.out.println(employee);
}
}

2.2.5 DTO

  • 如果多个参数不是业务模型中的数据,但是经常使用,可以考虑定义DTO(Data Transfer Object 数据传输对象)

2.2.6 #{}和${}取值的区别

  • #{}是以预编译的形式将参数设置到sql语句中,可以防止sql注入
  • ${}取出的值直接拼装在sql语句中,会有安全问题
  • 大多数情况下,取参数的值应该使用#{}的方式,在原生JDBC不支持占位符的地方就可以使用${}进行取值

2.2.7 #{}取值扩展用法

2.2.7.1 jdbcType

  • 当提供的参数为null的时候,有些数据库不能识别MyBatis对null的默认处理,比如Oracle就会报错
  • 默认情况下,MyBatis运行时设置项jdbcTypeForNull=OTHER,此时MyBatis对所有null都映射的是原生JDBC的OTHER类型,而oracle不认识
  • 可以在#{}取参数值的时候指定jdbcType,
1
2
3
<select id="getEmployeeByMap" resultType="com.lnhoo.Employee">
select * from employees where id=#{id} and last_name=#{lastName, jdbcType=NULL};
</select>
  • 也可以在全局配置文件中修改MyBatis运行时设置项,
1
2
3
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>

3 select

3.1 查询返回list

  • 在Mapper接口定义一方法,表示根据lastName查询数据表中符合条件的记录,
1
List<Employee> getEmployeeByLastName(String lastName);
  • Mapper xml配置文件中将sql语句和接口方法绑定,
1
2
3
<select id="getEmployeeByLastName" resultType="com.lnhoo.Employee">
select * from employees where last_name like "%${lastName}%"
</select>

这里虽然查询返回的结果集被封装成List集合,但是select标签的resultType属性要填集合中对象的类型,不能写成集合的类型

3.2 resultMap

3.2.1 自定义结果集映射规则

  • 可以使用resultMap标签自定义结果集的映射规则,
1
2
3
4
5
6
<resultMap id="Employee" type="com.lnhoo.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="email" property="email"></result>
<result column="gender" property="gender"></result>
</resultMap>
  • resultMap标签的id属性是resultMap定义的唯一标识,方便后续引用,type是Java Bean的全类名
  • resultMap标签内部的id标签用于定义数据库表主键和Java Bean中属性的映射关系,此处也可以使用result标签替代
  • result标签表明数据库表中的列和Java Bean属性的映射关系
  • 在select标签中引用resultMap来使用自定义的映射规则,
1
2
3
<select id="getEmployeeById" resultMap="Employee">
select * from employees where id=#{id}
</select>

3.2.2 关联查询

  • 创建部门表,然后往里头加一些数据,
1
2
3
4
5
6
7
8
9
10
11
12
13
create table departments (
id int(11) primary key auto_increment,
dept_name varchar(255)
)

insert into departments values (0, "Human Resource");
insert into departments values (0, "Infomation Technology")

select * from departments;
id|dept_name |
--+---------------------+
1|Human Resource |
2|Infomation Technology|
  • 为员工表加一个dept_id字段,
1
2
3
4
5
6
7
8
9
alter table employees add column dept_id int(11);
describe employees
Field |Type |Null|Key|Default|Extra |
---------+------------+----+---+-------+--------------+
id |int(11) |NO |PRI| |auto_increment|
last_name|varchar(255)|YES | | | |
gender |char(1) |YES | | | |
email |varchar(255)|YES | | | |
dept_id |int(11) |YES | | | |
  • 添加部门Department类,修改Employee类,
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class Department {
private int id;
private String name;

public Department() {

}

public Department(int id, String name) {
this.id = id;
this.name = name;
}

public void setId(int id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Department{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class Employee {
private Integer id;
private String lastName;
private char gender;
private String email;
private Department dept;

public Employee() {

}

public Employee(Integer id, String lastName, char gender, String email) {
this.id = id;
this.lastName = lastName;
this.gender = gender;
this.email = email;
}

public void setId(Integer id) {
this.id = id;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public void setGender(char gender) {
this.gender = gender;
}

public void setEmail(String email) {
this.email = email;
}

public void setDept(Department dept) {
this.dept = dept;
}

@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", gender=" + gender +
", email='" + email + '\'' +
", dept=" + dept +
'}';
}
}
  • 在Mapper xml配置文件中将接口方法和连接查询的sql语句绑定,
1
2
3
<select id="getEmployeeById" resultMap="Employee">
select e.id id, last_name, gender, email, d.id did, dept_name from employees e, departments d where e.dept_id=d.id and e.id=#{id}
</select>
  • 自定义查询结果集字段和Java Bean属性之间的映射关系,
1
2
3
4
5
6
7
8
<resultMap id="Employee" type="com.lnhoo.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="email" property="email"></result>
<result column="gender" property="gender"></result>
<result column="did" property="dept.id"></result>
<result column="dept_name" property="dept.name"></result>
</resultMap>

使用”.”操作符取出对象类型成员的属性,比如”dept.id”、”dept.name”

3.2.3 定义关联对象的封装规则

  • 在resultMap中使用association标签定义关联对象的封装规则,
1
2
3
4
5
6
7
8
9
10
<resultMap id="Employee" type="com.lnhoo.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="email" property="email"></result>
<result column="gender" property="gender"></result>
<association property="dept" javaType="com.lnhoo.Department">
<id column="did" property="id"></id>
<result column="dept_name" property="name"></result>
</association>
</resultMap>
  • association标签的property属性填成员变量名称,javaType填该成员的全类名

3.2.4 分步查询

  • 使用association进行分步查询,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap id="MyEmpByStep" type="com.lnhoo.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<association property="dept" select="com.lnhoo.dao.DepartmentMapper.getDepartmentById" column="dept_id">
<id column="id" property="id"></id>
<result column="dept_name" property="name"></result>
</association>
</resultMap>

<select id="getEmpByStep" resultMap="MyEmpByStep">
select * from employees where id=#{id};
</select>

association标签的select属性可支持分步查询,参数由column属性提供

3.2.5 延迟加载

  • 两个运行时设置项,
设置 功能 默认值
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态 false
aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 false(在 3.4.1 及之前的版本中默认为 true)
  • 所以要配置延迟加载,只需要在全局配置文件中设置两个开关,
1
2
3
4
5
<settings>
......
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
  • association、collection标签均有fetchType属性,可能值为“lazy”(延迟加载)、“eager”(立即加载)

3.2.6 定义关联集合封装规则

  • 通常员工和部门是多对一的关系,即一个部门拥有多个员工,一个员工只属于一个部门,
1
2
3
4
5
public class Department {
private int id;
private String name;
private List<Employee> employeeList;
............

假设Department对象包含一个员工列表employeeList

  • 使用collection标签定义关联集合封装规则,
1
2
3
4
5
6
7
8
9
10
<resultMap id="Department" type="com.lnhoo.Department">
<id column="id" property="id"></id>
<result column="dept_name" property="name"></result>
<collection property="employeeList" ofType="com.lnhoo.Employee">
<id column="eid" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
</collection>
</resultMap>
  • Mapper接口定义,
1
2
3
public interface DepartmentMapper {
Department getDepartmentById(Integer id);
}
  • 此时与接口绑定的查询sql配置为,
1
2
3
4
5
<select id="getDepartmentById" resultMap="Department">
select d.id id, dept_name, e.id eid, last_name, gender, email, dept_id from departments d
left join employees e on d.id=e.dept_id
where d.id=#{id}
</select>

3.2.7 关联集合分步查询

  • 假设要根据dept_id查询某个部门,Department对象有一employeeList字段,实现思路可以是:先根据部门id查询departments表得到部门名称,然后再根据部门id查询employees表得到属于该部门的所有员工填充到Department对象的employeeList属性中
  • 父查询:根据部门id查询对应的部门,
1
2
3
<select id="getDepartmentById" resultMap="Department">
select * from departments where id=#{id}
</select>
  • 子查询:根据部门id查询所属于该部门的所有员工,
1
2
3
<select id="getEmployeeByDeptId" resultType="com.lnhoo.Employee">
select * from employees where dept_id=#{id}
</select>
  • resultMap使用collection标签关联父、子查询,
1
2
3
4
5
6
7
8
9
10
<resultMap id="MyEmpByStep" type="com.lnhoo.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<association property="dept" select="com.lnhoo.dao.DepartmentMapper.getDepartmentById" column="dept_id">
<id column="id" property="id"></id>
<result column="dept_name" property="name"></result>
</association>
</resultMap>

3.2.8 分步查询传递多列值

  • association、collection标签分步查询如果要为select语句传递多个参数,需要封装成一个map,
1
column="{key1: column1, key2: column2}"

3.2.9 鉴别器

  • discriminator可以根据查询结果集的某个列决定封装的对象
  • 假如有个需求,要求:

(1)只有女生才查询所属的部门

(2)男生的email被赋值成last_name

可以使用鉴别器实现如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<resultMap id="MyEmpByStep" type="com.lnhoo.Employee">
<discriminator javaType="string" column="gender">
<case value="0">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<association property="dept" select="com.lnhoo.dao.DepartmentMapper.getDepartmentById" column="dept_id">
<id column="id" property="id"></id>
<result column="dept_name" property="name"></result>
</association>
</case>
<case value="1">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="last_name" property="email"></result>
</case>
</discriminator>
</resultMap>

OpenGL新建窗口

1 安装依赖包

1
2
3
sudo apt install libgl1-mesa-dev
sudo apt install libglew-dev libsdl2-dev libsdl2-image-dev libglm-dev libfreetype6-dev
sudo apt install libglfw3-dev libglfw3

2 Hello Window

2.1 初始化glfw

1
2
3
4
5
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  • 调用glfwInit函数初始化glfw
  • glfwWindowHint函数有两个参数,前一个参数指代某个glfw设置项,后一个参数是一个整型值
  • GLFW_CONTEXT_VERSION_MAJOR和GLFW_CONTEXT_VERSION_MINOR分别是指定的OpenGL版本号,例如这里是3.3版本
  • GLFW_OPENGL_PROFILE配置为GLFW_OPENGL_CORE_PROFILE,表明使用OpenGL核心模式
  • GLFW_OPENGL_FORWARD_COMPAT设置为GL_TRUE,保证程序的前向兼容性

2.2 创建窗口

1
2
3
4
5
6
7
8
9
10
// 创建一个窗口
GLFWwindow *window = glfwCreateWindow(800, 600, "Hello, OpenGL", nullptr, nullptr);
if (!window) {
std::cout << "create window failed." << std::endl;
glfwTerminate();
return -1;
}

// 将OpenGL上下文和新创建的窗口绑定
glfwMakeContextCurrent(window);
  • glfwCreateWindow函数创建一个窗口,参数分别是,
参数 作用
width 窗口宽度
height 窗口高度
title 窗口标题
monitor 监视器(全屏模式下需指定)
share 共享上下文的另一个窗口(没有就不需要给定,传递NULL即可)
  • glfwMakeContextCurrent函数将当前OpenGL上下文和窗口绑定

2.3 初始化glew

1
2
3
4
5
if (glewInit() != GLEW_OK) {
std::cout << "glew init failed." << std::endl;
glfwTerminate();
return -1;
}
  • 调用glewInit函数初始化glew,如果失败,就调用glfwTerminate函数退出整个程序
  • GLEW 是OpenGL扩展库,使用它可以很方便的调用OpenGL较新的特性。GLEW能自动识别你的平台所支持的全部OpenGL高级扩展涵数,也就是说,只要包含一个glew.h头文件,你就能使用gl, glu, glext, wgl, glx的全部函数

2.4 视口

  • 视口(View Port)是窗口内的一块可绘制区域,可以调用glViewport函数为某个窗口指定视口,
1
2
3
4
5
6
void framebuffer_size_callback(GLFWwindow *window, int width, int height) {
// 左下角坐标、宽度、高度
glViewport(0, 0, width, height);
}

glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
  • glfwSetFramebufferSizeCallback设置窗口大小改变时的回调函数,在这里,重置视口的左下角坐标和宽度、高度

2.5 渲染循环

1
2
3
4
while(!glfwWindowShouldClose(window)) {
glfwSwapBuffers(window);
glfwPollEvents();
}
  • glfwWindowShouldClose函数判断给定的窗口是否应该被关闭,通常是用户鼠标点击窗口右上方的叉时
  • glfwSwapBuffers函数将前缓冲(front buffer)和后缓冲(back buffer)的内容交换,因为渲染应用了双缓冲技术,前缓冲的内容直接被展现在显示器上,而所有的的渲染指令都会在后缓冲上绘制
  • glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)

2.6 处理输入

1
2
3
4
5
void processInput(GLFWwindow *window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
}
  • 调用glfwGetKey函数可以得到一个按键当前的状态,GLFW_PRESS代表按下,GLFW_RELEASE

代表松开

  • glfwSetWindowShouldClose函数设置窗口是否应该被关闭

2.7 清空窗口

1
2
3
// redering
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
  • glClearColor函数设置清空窗口的底色,而glCear执行实际的清空操作,这里参数传递GL_COLOR_BUFFER_BIT表明此举是为了将窗口颜色清空

MyBatis全局配置文件

1 引入dtd约束

  • 文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构,DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用
  • 在MyBatis全局配置文件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">

引入了MyBatis的文档类型定义文件,这样在IDE中编辑就可以出现xml标签和属性的自动提示

  • 在Mapper配置文件的头部也有引入dtd文件,
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">

2 引入外部配置文件

  • 可以使用properties标签引入外部配置文件,
1
2
3
4
5
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=123456
<properties resource="dbsource.properties"></properties>
  • properties标签可以用两种方式表示外部配置文件的地址,
属性 含义
resource 从本地项目类路径下获取外部配置文件
url 从网络url远程获取外部配置文件

3 运行时行为设置

  • 可以通过settings标签配置MyBatis的运行时行为设置项,
1
2
3
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

在这个例子中,将运行时行为mapUnderscoreToCamelCase的值设置为true,MyBatis就会将数据库中下划线表示的字段自动映射到Java类中驼峰命名的属性上,即从经典数据库列名A_COLUMN映射到经典Java属性名aColumn

4 类型别名

4.1 typeAlias

  • 可以使用typeAlias标签为Java类起别名,
1
2
3
<typeAliases>
<typeAlias type="com.lnhoo.Employee" alias="emp"></typeAlias>
</typeAliases>
  • 相应的,在Mapper配置文件中也需要修改resultType,
1
2
3
4
5
<mapper namespace="com.lnhoo.dao.EmployeeMapper">
<select id="getEmployeeById" resultType="emp">
select * from employees where id = #{id}
</select>
</mapper>

4.2 package标签

  • 在typeAliases标签内部,可以通过package标签批量为包下的所有类起别名,
1
2
3
<typeAliases>
<package name="com.lnhoo"/>
</typeAliases>

此时类的别名是类名的小写形式,比如Employee类的别名就是employee

4.3 @Alias注解

  • 在使用package标签批量起别名的时候,可能会造成名字冲突,此时可以在Java类上面应用@Alias注解起别名,
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
@Alias("emp")
public class Employee {
private Integer id;
private String lastName;
private char gender;
private String email;

public void setId(Integer id) {
this.id = id;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public void setGender(char gender) {
this.gender = gender;
}

public void setEmail(String email) {
this.email = email;
}

@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", gender=" + gender +
", email='" + email + '\'' +
'}';
}
}

5 类型处理器

  • MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成Java类型

6 插件

  • MyBatis允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件来拦截的方法调用包括:

(1)Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

(2)ParameterHandler (getParameterObject, setParameters)

(3)ResultSetHandler (handleResultSets, handleOutputParameters)

(4)StatementHandler (prepare, parameterize, batch, update, query)

7 运行环境

  • 可以在environments标签内部通过environment标签配置多种运行环境,
1
2
3
4
5
6
7
8
9
10
11
<environments default="development">
<environment id="development">
<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>

大多数情况下,开发环境、测试环境的数据源是相互隔离的,所以需要两份配置

  • 环境必须提供事务管理器和数据源配置信息
  • 可以通过设置environments标签的default属性在多个环境之间快速切换
  • 使用dataSource标签配置数据源,内置的数据源包括:POOLED(带连接池)、UNPOOLED(不带连接池)、JNDI
  • 自定义数据源:实现DataSourceFactory接口,在dataSource标签的type属性填上全类名

8 多数据库支持

  • MyBatis可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的databaseId属性。 MyBatis会加载带有匹配当前数据库databaseId属性和所有不带databaseId属性的语句, 如果同时找到带有databaseId和不带databaseId的相同语句,则后者会被舍弃
1
2
3
4
5
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>
  • 在Mapper配置文件中,
1
2
3
4
5
6
7
8
9
10
11
<mapper namespace="com.lnhoo.dao.EmployeeMapper">
<select id="getEmployeeById" resultType="emp">
select * from employees where id = #{id}
</select>
<select id="getEmployeeById" resultType="emp" databaseId="mysql">
select * from employees where id = #{id}
</select>
<select id="getEmployeeById" resultType="emp" databaseId="oracle">
select id, last_name, email from tbl_employees where id = #{id}
</select>
</mapper>

使用databaseId属性指定SQL语句执行所在的数据库

9 SQL映射注册

9.1 注册配置文件

1
2
3
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
  • 使用mapper标签在MyBatis全局配置文件中注册SQL映射配置文件
  • resource属性从类路径加载xml配置文件,如果是url属性,则从网络或磁盘位置加载xml配置文件

9.2 注册接口

  • 使用注解方式实现Mapper接口中方法和预执行的SQL语句的绑定,
1
2
3
4
public interface EmployeeMapperAnnotation {
@Select(value = "select * from employees where id=#{id}")
Employee getEmployeeById(Integer id);
}
  • 在MyBatis全局配置文件中引入SQL映射接口,
1
2
3
<mappers>
<mapper class="com.lnhoo.dao.EmployeeMapperAnnotation"></mapper>
</mappers>

MyBatis安装、配置

1 简介

1.1 概述

  • MyBatis是一个半自动化的持久化层框架

1.2 为什么要使用MyBatis

  • 对于开发人员而言,核心SQL还是需要自己优化
  • SQL和Java编码分开,功能边界清晰,一个专注业务,一个专注数据

1.2.1 JDBC

  • SQL夹在Java代码块里,耦合度高导致硬编码内伤
  • 维护不易,且实际开发需求中SQL频繁修改的情况是多见的

1.2.2 Hibernate

  • 长难SQL对于Hibernate而言处理也不容易
  • 内部自动产生的SQL,不容易做特殊优化
  • 基于全映射的全自动框架,大量自动的POJO进行部分映射时比较困难,导致数据库性能下降

2 idea导入MyBatis

  • 使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>

“x.x.x”替换成对应的版本号

3 Hello world

3.1 创建MySQL数据表、添加数据

1
2
3
4
5
6
7
8
9
10
11
12
13
create table employees (
id int(11) primary key auto_increment,
last_name varchar(255),
gender char(1),
email varchar(255)
)

insert into employees (id, last_name, gender, email) values (1, "tom", 0, "136xxxx@qq.com")

select * from employees e
id|last_name|gender|email |
--+---------+------+--------------+
1|tom |0 |136xxxx@qq.com|

3.2 从 XML 中构建 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.lnhoo;

public class Employee {
private Integer id;
private String lastName;
private char gender;
private String email;

public void setId(Integer id) {
this.id = id;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public void setGender(char gender) {
this.gender = gender;
}

public void setEmail(String email) {
this.email = email;
}

@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", gender=" + gender +
", email='" + email + '\'' +
'}';
}
}
// 测试类
public class MyBatisTest {
@Test
public void testMapper() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

try (SqlSession session = sqlSessionFactory.openSession()) {
Employee employee = (Employee) session.selectOne("com.lnhoo.EmployeeMapper.selectEmployee", 1);
System.out.println(employee);
}
}
}
  • 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

3.3 mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="******"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
  • XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)

3.4 Mapper配置文件

1
2
3
4
5
6
7
8
9
<?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.lnhoo.EmployeeMapper">
<select id="selectEmployee" resultType="com.lnhoo.Employee">
select id id, last_name lastName, gender gender, email email from employees where id = #{id}
</select>
</mapper>
  • 在数据库表中的字段last_name对应于Employee类中的lastName属性,因为命名不一致,所以需要在SQL语句中将last_name字段起别名为lastName

3.5 接口式编程

3.5.1 定义接口

1
2
3
public interface EmployeeMapper {
Employee getEmployeeById(Integer id);
}
  • 在接口中定义方法的参数和返回值

3.5.2 定义Mapper

1
2
3
4
5
6
7
8
9
<?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.lnhoo.dao.EmployeeMapper">
<select id="getEmployeeById" resultType="com.lnhoo.Employee">
select id id, last_name lastName, gender gender, email email from employees where id = #{id}
</select>
</mapper>
  • 通过xml文件配置接口和SQL语句的绑定关系
  • mapper标签的namespace属性必须和Mapper接口的全路径一致
  • select标签的id属性必须和接口中定义的方法名一致,resultType属性填返回对象所属类的全路径

3.5.3 调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyBatisTest {
@Test
public void testMapper() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.getEmployeeById(1);
System.out.println(employee);
}
}
}
  • 将接口和xml配置文件绑定后,MyBatis会自动为接口创建动态代理对象,由代理对象去执行增删改查

Spring AOP(面向切面编程)

1 概念

1.1 什么是AOP

  • AOP,即面向切面编程,可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率
  • 通俗描述:不通过修改源代码的方式,在主干功能里面添加新功能

2 原理

2.1 动态代理

2.1.1 JDK动态代理

  • 有接口的情况,使用JDK动态代理,创建接口实现类的代理对象

2.1.1.1 Proxy类

  • Proxy类提供了newProxyInstance方法用于创建代理对象,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.

* @param loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class
* to implement
* @param h the invocation handler to dispatch method invocations to
* @return a proxy instance with the specified invocation handler of a
* proxy class that is defined by the specified class loader
* and that implements the specified interfaces

*/
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler );

2.1.1.2 创建方式

  • 假设有一接口,
1
2
3
public interface UserDao {
public int add(int a, int b);
}
  • 这个接口有一实现类,
1
2
3
4
5
6
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
return a + b;
}
}
  • 实现调用处理器,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserDaoProxy implements InvocationHandler {
private Object object;

public UserDaoProxy(Object object) {
this.object = object;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before invoke");
Object result= method.invoke(object, args);
System.out.println("after invoke");
return result;
}
}
  • 创建代理对象,
1
2
3
4
5
UserDao userDao = new UserDaoImpl();
Class[] interfaces = {UserDao.class};
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(UserDao.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = userDaoProxy.add(1, 2);
System.out.println("result: " + result);

2.1.2 CGLIB动态代理

  • 没有接口的情况,使用CGLIB动态代理,创建当前类子类的代理对象

3 AOP术语

3.1 连接点

  • 类里面可以被增强的方法称为连接点

3.2 切入点

  • 实际被增强的方法称为切入点

3.3 通知(增强)

  • 实际增强的逻辑部分称为通知(增强)
  • 通知有5种类型:

(1)前置通知

(2)后置通知

(3)环绕通知

(4)异常通知

(5)最终通知

3.4 切面

  • 切面,是把通知应用到切入点的过程

4 实现AOP操作

4.1 AspectJ

  • AspectJ不是Spring的组成部分,是独立的AOP框架,一般将AspectJ和Spring搭配使用进行AOP操作
  • 基于AspectJ实现AOP操作:

(1)基于xml配置文件实现

(2)基于注解方式实现

4.2 导入依赖

  • 使用Maven配置项目依赖,
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
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>

<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>

4.3 切入点表达式

4.3.1 作用

  • 让Spring框架知道对哪个类里的哪个方法进行增强

4.3.2 语法结构

  • execution([权限修饰符][返回类型][类全路径][方法名称][参数列表])
  • 权限修饰符、返回类型可用“*”通配
  • 类的名称可用“*”通配
  • 方法名称可用“*”通配
  • 举例:

(1)* com.company.dao.BookDao.add(..)

(2)* com.company.dao.BookDao.*(..)

(3)* com.company.dao..(..)

4.4 AspectJ注解

4.4.1 创建被增强类,在类里面定义方法

1
2
3
4
5
6
@Component
public class User {
public void add() {
System.out.println("User add......");
}
}

4.4.2 创建增强类(编写增强逻辑)

  • 在增强类里面创建方法,让不同的方法代表不同的通知类型
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
@Component
public class UserProxy {
public void before() {
System.out.println("before......");
}

public void after() {
System.out.println("after......");
}

public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around before......");

proceedingJoinPoint.proceed();

System.out.println("around after......");
}

public void afterReturning() {
System.out.println("after returning......");
}

public void afterThrowing() {
System.out.println("after throwing......");
}
}

4.4.3 在Spring配置文件中开启组件扫描

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 开启组件扫描-->
<context:component-scan base-package="com.lnhoo"></context:component-scan>

4.4.4 在增强类上面注解Aspect

1
2
3
4
5
@Component
@Aspect
public class UserProxy {
......
}

4.4.5 在Spring配置文件中开启AspectJ自动代理

1
2
<!--    开启AspectJ自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

4.4.6 配置不同类型的通知

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
@Component
@Aspect
public class UserProxy {
// 前置通知
@Before(value = "execution(* com.lnhoo.User.add())")
public void before() {
System.out.println("before......");
}

// 最终通知
@After(value = "execution(* com.lnhoo.User.add())")
public void after() {
System.out.println("after......");
}

// 环绕通知
@Around(value = "execution(* com.lnhoo.User.add())")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around before......");

proceedingJoinPoint.proceed();

System.out.println("around after......");
}

// 后置通知
@AfterReturning(value = "execution(* com.lnhoo.User.add())")
public void afterReturning() {
System.out.println("after returning......");
}

// 异常通知
@AfterThrowing(value = "execution(* com.lnhoo.User.add())")
public void afterThrowing() {
System.out.println("after throwing......");
}
}
  • @Before,前置通知,在被增强的方法前执行
  • @AfterReturning,后置通知,在被增强的方法正常返回后执行(不包括抛异常的情况)
  • @Around,环绕通知,在被增强方法的前、后执行
  • @After,最终通知,无论如何都在被增强方法之后执行
  • @AfterThrowing,异常通知,只有在被增强方法抛出异常后执行

4.4.7 抽取公共切入点

  • 使用Pointcut注解抽取公共切入点,
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
@Component
@Aspect
public class UserProxy {

@Pointcut(value = "execution(* com.lnhoo.User.add())")
private void methodExtending() {

}

@Before(value = "methodExtending()")
public void before() {
System.out.println("before......");
}

@After(value = "methodExtending()")
public void after() {
System.out.println("after......");
}

@Around(value = "methodExtending()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around before......");

proceedingJoinPoint.proceed();

System.out.println("around after......");
}

@AfterReturning(value = "methodExtending()")
public void afterReturning() {
System.out.println("after returning......");
}

@AfterThrowing(value = "methodExtending()")
public void afterThrowing() {
System.out.println("after throwing......");
}
}

4.4.8 设置增强类优先级

  • 在增强类上面添加注解@Order(数字类型值),数字类型值越小优先级越高

项目管理工具Maven的使用

1 简介

1.1 概述

  • Maven是一款项目管理工具,将项目开发和管理过程抽象成一个项目对象模型(POM)

1.2 功能

  • 项目构建:提供标准的、跨平台的自动化项目构建方式
  • 依赖管理:方便快捷地管理项目依赖的资源(jar包),避免资源间的版本冲突问题
  • 统一开发结构:提供标准、统一的项目结构

2 安装配置

2.1 下载Maven

  • 下载地址,

https://maven.apache.org/download.cgi

img

如果是Windows平台,选择zip安装包下载

2.2 解压、移动文件

  • 解压zip文件,

img

将maven根目录移动到一个固定的目录下,

img

2.3 配置环境变量

2.3.1 MAVEN_HOME

  • 新建一个系统环境变量,命名为MAVEN_HOME,值为maven根目录的路径

img

2.3.2 Path

  • 编辑环境变量Path,在末尾添加maven bin目录的路径,

img

2.4 测试

  • 重新打开一个cmd窗口,执行命令mvn,

img

如果出现上图中的提示信息,说明安装成功

3 基本概念

3.1 仓库

  • 仓库用于存储资源,包含各种jar包

img

3.1.1 分类

  • 本地仓库:自己电脑上存储资源的仓库,必要时从远程仓库获取资源
  • 远程仓库:

(1)中央仓库:Maven团队维护,存储绝大多数资源

(2)私服:部门/公司范围内存储资源的仓库,必要时从中央仓库获取资源

3.1.2 私服的作用

  • 保存具有版权的资源,包含购买或自主研发的jar包
  • 一定范围内共享资源,仅对内部开发,不对外共享

3.2 坐标

3.2.1 作用

  • Maven中的坐标用于描述仓库中资源的位置
  • 使用唯一标识,唯一性定位资源位置,通过该标识可以将资源的识别与下载工作交由机器完成

3.2.1 组成

  • groupId:当前Maven项目隶属的组织名称(通常是域名反写,例如:org.mybatis)
  • artifactId:当前Maven项目名称(通常是模块名称,例如CMS、SMS)
  • version:当前版本号

3.3 仓库配置

3.3.1 本地仓库路径

  • 打开%MAVEN_HOME%\conf目录下的settings.xml配置文件,定位到localRepository标签,

img

添加一个localRepository标签,内部填上本地仓库的路径(默认为:${user.home}/.m2/repository)

3.3.2 镜像仓库地址

  • 定位到mirros标签,在内部添加如下一段配置,
1
2
3
4
5
6
<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<name>Aliyun Public Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>

将Aliyun Maven仓库设置为中央仓库的镜像

4 实践

4.1 目录结构

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
$ tree maven_demo/
maven_demo/
├── pom.xml # POM配置文件
├── src # 存放项目源代码文件
│ ├── main # 存放项目的主要代码
│ │ ├── java # 存放java源代码文件
│ │ │ └── Demo.java
│ │ └── resources # 存放项目配置文件(比如Spring的xml配置文件)
│ └── test
│ ├── java # 存放测试所用的java源代码
│ │ └── DemoTest.java
│ └── resources # 存放测试所用的配置文件
└── target # 项目编译后产生的一个目录,主要存放的是编译后的.class文件
├── classes
│ └── Demo.class
├── generated-sources
│ └── annotations
├── generated-test-sources
│ └── test-annotations
├── maven-status
│ └── maven-compiler-plugin
│ └── compile
│ └── default-compile
│ ├── createdFiles.lst
│ └── inputFiles.lst
└── test-classes
└── DemoTest.class

4.2 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
<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">
<!-- POM模型版本 -->
<modelVersion>4.0.0</modelVersion>
<!-- 公司或组织的唯一标识 -->
<groupId>com.company</groupId>
<!-- 项目的唯一标识 -->
<artifactId>maven-demo</artifactId>
<!-- 项目版本号 -->
<version>0.0.1-SNAPSHOT</version>
<!-- 打包方式 -->
<packaging>jar</packaging>

<!-- 定义项目依赖-->
<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<!-- 公司或组织的唯一标识 -->
<groupId>junit</groupId>
<!-- 项目的唯一标识 -->
<artifactId>junit</artifactId>
<!-- 项目版本号 -->
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

4.3 常用命令

4.3.1 项目构建:mvn compile

  • 在maven项目根目录下执行mvn compile命令,

img

maven将自动下载项目所需依赖和maven插件,然后进行构建

4.3.2 项目测试:mvn test

img

img

  • 除了在控制台展示测试结果,还在target\surefire-reports目录下生成测试数据文件

4.3.3 清理编译产物:mvn clean

img

  • mvn clean命令将整个target目录删除

4.3.4 打包:mvn package

img

img

  • 打包的过程需要经历:主程序编译、测试程序编译、执行测试程序、最终打包,然后jar(war)包会被存放在target目录下,其中只包含主程序,不包含测试程序

4.3.5 安装本地仓库:mvn install

  • mvn install命令依然会经过:主程序编译、测试程序编译、执行测试程序、打包等步骤

4.4 依赖管理

  • 依赖指当前项目运行所需的jar包,一个项目可以设置多个依赖

4.4.1 配置格式

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 定义项目依赖-->
<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<!-- 项目所属公司或组织的唯一标识-->
<groupId>junit</groupId>
<!-- 项目的唯一标识-->
<artifactId>junit</artifactId>
<!-- 项目版本号-->
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>

4.4.2 依赖传递

4.4.2.1 依赖的传递性

  • 直接依赖:在当前项目中通过依赖配置建立的依赖关系
  • 间接依赖:被依赖的资源如果依赖其他的资源,当前项目间接依赖其他资源

4.4.2.2 依赖传递冲突问题

  • 路径优先:当依赖中出现相同的资源时,层级越深,优先级越低;层级越浅,优先级越高
  • 声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖配置顺序靠后的

4.4.3 可选依赖(不透明)

  • 可选依赖指对外隐藏当前所依赖的资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 定义项目依赖-->
<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<!-- 目所属公司或组织的唯一标识 -->
<groupId>junit</groupId>
<!-- 项目的唯一标识 -->
<artifactId>junit</artifactId>
<!-- 项目版本号 -->
<version>4.13.2</version>
<!-- 配置可选依赖 -->
<optional>true</optional>
</dependency>
</dependencies>

4.4.4 排除依赖(不需要)

  • 排除依赖指主动断开依赖的资源,被排除的资源无需指定版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 定义项目依赖-->
<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<!-- 项目所属公司或组织的唯一标识-->
<groupId>junit</groupId>
<!-- 项目的唯一标识-->
<artifactId>junit</artifactId>
<!-- 项目版本号-->
<version>4.13.2</version>
<!-- 配置可选依赖-->
<optional>true</optional>
<!-- 排除依赖-->
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

4.4.5 依赖范围

  • 依赖的jar默认情况可以在任何地方使用,可以通过scope标签设定其依赖范围

4.4.5.1 依赖作用范围

  • 主程序范围有效(main文件夹范围内)
  • 测试程序范围有效(test文件夹范围内)
  • 是否参与打包(package指令范围内)
scope 主代码 测试代码 打包 范例
compile(默认) Y Y Y log4j
test Y junit
provided Y Y servlet-api
runtime Y jdbc

4.5 生命周期

4.5.1 什么是Maven构建生命周期

  • Maven构建生命周期描述的是一次构建过程经历的事件

4.5.2 阶段

  • clean:清理工作
  • default:核心工作,例如编译、测试、打包、部署等
  • site:产生报告,发布站点等

4.5.3 clean生命周期

  • pre-clean:执行一些需要在clean之前完成的工作
  • clean:移除所有上一次构建生成的文件
  • post-clean:执行一些需要在clean之后立刻完成的工作

4.5.4 default生命周期

生命周期阶段 描述
validate 验证项目是否正确,并且所有必要的信息可用于完成构建过程
initialize 建立初始化状态,例如设置属性
generate-sources 产生任何的源代码包含在编译阶段
process-resources 复制和处理资源到目标目录,准备打包阶段
compile 编译该项目的源代码
process-classes 从编译生成的文件提交处理,例如:Java类的字节码增强/优化
generate-test-sources 生成任何测试的源代码包含在编译阶段
process-test-sources 处理测试源代码,例如:过滤器任何值
test-compile 编译测试源代码到测试目标目录
process-test-classes 处理测试代码文件编译生成的文件
test 运行测试使用合适的单元测试框架(JUnit)
prepare-package 执行必要的任何操作的实际打包之前准备一个包
package 提取编译后的代码,并在其分发格式打包,如JAR、WAR或EAR文件
pre-integration-test 完成执行集成测试之前所需操作。例如,设置所需的环境
integration-test 处理并在必要时部署软件包到集成测试可以运行的环境
post-integration-test 完成集成测试已全部执行后所需操作。例如:清理环境
verify 运行任何检查,验证包是有效的,符合质量审核规定
install 将包安装到本地存储库,它可以用作当地其他项目的依赖
deploy 复制最终的包到远程仓库与其他开发者和项目共享

4.5.6 site生命周期

  • pre-site:执行一些需要在生成站点文档之前完成的工作
  • site:生成项目的站点文档
  • post-site:执行一些需要在生成站点文档之后完成的工作,并且为部署做准备
  • site-deploy:将生成的站点文档部署到服务器上

4.6 插件

  • 插件与生命周期内的阶段绑定,在执行到对应生命周期时执行对应的插件功能
  • 默认Maven在各个生命周期上绑定有预设的功能
  • 通过插件可以自定义其他功能,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>