SSM & SpringBoot 138问138答

SSM 73问

mybaits 21问

Mybaits与Hibernate的异同?

Hibernate与MyBatis都是ORM框架,都有相应的代码生成工具,可以生成简单基本的DAO层方法。

Mybaits是半ORM框架,Hibernate是全ORM框架

Mybaits需要手动写SQL语句,Hibernate不需要

什么是ORM?

对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。

目的是使用面向对象的方法操纵数据库

mybatis怎么配置环境信息?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<environments default="development">
<!-- default:默认的环境 ID-->
<environment id="development">
<!-- 事务管理器的配置
如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器
- JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务范围。
- MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期
-->
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<!-- 数据源的配置-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

mybaits的setting有什么作用?

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。下表描述了设置中各项的意图、默认值等。

设置参数描述有效值默认值
cacheEnabled该配置影响的所有映射器中配置的缓存的全局开关。true,falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。true,falsefalse
aggressiveLazyLoading当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。true,falsefalse (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled是否允许单一语句返回多结果集(需要兼容驱动)。true,falsetrue
useColumnLabel使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。true,falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。true,falseFalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。SIMPLE REUSE BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定驱动等待数据库响应的秒数。Any positive integerNot Set (null)
safeRowBoundsEnabled允许在嵌套语句中使用分页(RowBounds)。true,falseFalse
mapUnderscoreToCamelCase是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。true, falseFalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。SESSION,STATEMENTSESSION
jdbcTypeForNull当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType enumeration. Most common are: NULL, VARCHAR and OTHEROTHER
lazyLoadTriggerMethods指定哪个对象的方法触发一次延迟加载。A method name list separated by commasequals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成的默认语言。A type alias or fully qualified class name.org.
apache
.ibatis.
scripting.
xmltags.
XMLDynamicLanguageDriver
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。true,falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。Any StringNot set
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J
,LOG4J
,LOG4J2,
JDK_LOGGING,
COMMONS_LOGGING,
STDOUT_LOGGING,
NO_LOGGING
Not set
proxyFactory

指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。CGLIB JAVASSISTCGLIB

vfslmpl

指定 VFS 的实现自定义 VFS 的实现的类全限定名,以逗号分隔。no set

useActualParamName

允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)true \false
configurationFactory

指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)类型别名或者全类名.no set

一个配置完整的 settings 元素的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

如何配置类型别名?

类型别名是为 Java 类型设置一个短的名字,存在的意义仅在于用来减少类完全限定名的冗余。例如:

1
2
3
4
5
6
7
8
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

当这样配置时,Blog可以用在任何使用domain.blog.Blog的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

1
2
3
<typeAliases>
<package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。看下面的例子:

1
2
3
4
@Alias("author")
public class Author {
...
}

三种数据源类型的区别?

UNPOOLED– 这个数据源的实现只是每次被请求时打开和关闭连接。虽然一点慢,它对在及时可用连接方面没有性能要求的简单应用程序是一个很好的选择。

POOLED– 这种数据源的实现利用”池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。

JNDI– 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

如何指定映射文件?

最佳的方式是告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 使用相对类路径定义资源 -->  
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符 -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<!-- 使用包名+类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!-- 注册包内所有mapper -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>

select与属性的用法?

简单查询的 select 元素是非常简单的。比如:

1
2
3
4
5
6
7
8
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
<!--
id:查询的名称
parameterType:参数类型
resultType:返回值类型
-->

select 元素有很多属性允许你配置,来决定每条语句的作用细节。

1
2
3
4
5
6
7
8
9
10
11
12
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10000"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">

属性的含义:

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

insert、update、delete的用法?

数据变更语句 insert,update 和 delete 的实现非常接近:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">

<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">

<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">

Insert, Update 和 Delete 的属性

属性描述
id命名空间中的唯一标识符,可被用来代表这条语句。
parameterType将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
flushCache将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。
statementTypeSTATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys(仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
keyProperty(仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
databaseId如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。

下面就是 insert,update 和 delete 语句的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<insert id="insertAuthor">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>

<delete id="deleteAuthor">
delete from Author where id = #{id}
</delete>

两种字符串替换的区别?

#{}${}
参数占位符,即预编译字符串替换符,即SQL拼接
很大程度上能防止sql 注入不能防止sql 注入
将传入的数据都当成一个字符串,会对传入的变量自动加一个单引号将传入的参数直接显示生成在sql中,且不加任何引号
排序时使用order by 动态参数时需要注意,用$而不是#

三种自动映射等级?

1
<setting name="autoMappingBehavior" value="PARTIAL"/>

NONE 表示取消自动映射

PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集

FULL 会自动映射任意复杂的结果集(无论是否嵌套)

if标签的作用?

1
2
3
4
5
6
7
8
9
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
<!-- 如果test为true则语句包含if内的内容-->
AND title like #{title}
</if>
</select>

foreach标签的用法?

1
2
3
4
5
6
7
8
9
10
11
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
<!-- foreach 允许你指定一个集合,声明可以用在元素体内的集合项和索引变量-->
<!-- 也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。-->

怎么配置日志级别?

1
2
3
4
# 全局日志等级
log4j.rootLogger=ERROR, stdout
# mabatis mapper日志等级
log4j.logger.org.mybatis.example.BlogMapper=TRACE

一级缓存和二级缓存的定义与区别?

  • 一级缓存
    • 定义
      • 一级缓存作用域是sqlsession级别的,同一个sqlsession中执行相同的sql查询(相同的sql和参数),第一次会去查询数据库并写到缓存中,第二次从一级缓存中取。
      • 一级缓存是基于 PerpetualCache 的 HashMap 本地缓存,默认打开一级缓存。
    • 清空一级缓存
      • 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
      • 一级缓存时执行commit,close,增删改等操作,就会清空当前的一级缓存;当对SqlSession执行更新操作(update、delete、insert)后并执行commit时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)。
    • 一级缓存无过期时间,只有生命周期
  • 二级缓存
    • 简介
      • 它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
      • 二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。 、
    • 何时存入
      • 在关闭sqlsession后(close),才会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。
      • 开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中。
    • 二级缓存有过期时间,但没有后台线程进行检测
      • 需要注意的是,并不是key-value的过期时间,而是这个cache的过期时间,是flushInterval,意味着整个清空缓存cache,所以不需要后台线程去定时检测。
      • 每当存取数据的时候,都要检测一下cache的生命时间,默认是1小时,如果这个cache存活了一个小时,那么将整个清空一下。
    • 当 Mybatis 调用 Dao 层查询数据库时,先查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找。

useCache与flushCache的作用?

1
2
3
4
5
6
 <select
flushCache="false"
useCache="true"
>
<!--useCache:将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true。-->
<!--flushCache:将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。-->

Redis的概念?

Redis 是一个高性能的key-value数据库。经常用作缓存

那个注解是对dao组件的修饰?

1
2
@Mapper //Mybatis 需要找到对应的 mapper,在编译的时候动态生成代理类
@Repository //用于标注数据访问组件,即DAO组件

JavaModelGenerator配置、sqlMapperGenerator配置与JavaClientGenerator配置的作用?

  • JavaModelGenerator配置生成的实体类的存放的位置(entity层的java文件)
  • sqlMapperGenerator配置生成的映射文件的位置(resources/dao中的xml文件)
  • JavaClientGenerator配置生成的mapper接口文件的位置(dao层中的java文件)

如何实现分页?

导入依赖包

1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>

配置properties

1
2
3
4
5
#pagehelper配置
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
pagehelper.params=count=countSql

使用

1
2
3
4
5
6
7
8
9
10
11
@Transactional(readOnly = true)
@Override
public PageInfo<Student> getStudentListByBatchName(String batchName, Integer pageNo) {
//spring boot程序中,添加了PageHelper的启动依赖后,直接调用对应方法分页查询即可
PageHelper.startPage(pageNo, 5);
List<Student> list = studentMapper.getListByBatchName(batchName);
//传入查询的列表,创建一个PageInfo对象,用于包含分页的所有信息
//还可以传入第二个参数,表示导航页码的数量
PageInfo<Student> pageInfo=new PageInfo<>(list,5);
return pageInfo;
}

分页最后封装成什么数据?

PageInfo<T>

spring Framework 40问

依赖注入的概念?

Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系.

控制反转核心思想就是由 Spring 负责对象的创建。DI是IOC的一种。在对象创建过程中,Spring 会自动根据依赖关系,将它依赖的对象注入到当前对象中,这就是所谓的“依赖注入”。

springCoreContainer是什么?

img.png

  • Core:核心工具包,包括字节码操作cglib、asm,资源的抽象Resource,对象实例化化工具等等。
  • Beans:Bean 的定义、Bean 的创建以及对 Bean 的解析。
  • Context:Context模块建立在Core和Beans模块之上,是Bean运行环境(即保存维护Bean的状态、数据,Bean之间的关系),又称之为Ioc容器。
  • SpEL:提供了一个强大的表达式语言,可以在运行时查询和操作对象。

springIoC容器的类型?

spring BeanFactory 容器

  • 是Spring bean容器的根接口,提供获取bean,是否包含bean,是否单例与原型,获取bean类型,bean 别名的方法 。
  • BeanFactory不支持国际化功能
  • 不支持事件机制
  • 没有扩展ResourceLoader,只能加载一个Resource
  • BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化
  • BeanFactory需要手动注册

Spring ApplicationContext 容器

  • ApplicationContext继承了BeanFactory
  • 扩展了MessageResource接口,因而具有消息处理的能力(i18N)
  • 通过ApplicationEvent和ApplicationListener这两个接口实现事件机制
  • 扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource
  • 在容器启动时,一次性创建了所有的Bean
  • 而ApplicationContext则是自动注册

ApplicationContext容器的实现有哪些?

ApplicationContext 有两个直接子接口:WebApplicationContext 和 ConfigurableApplicationContext。

ConfigurableApplicationContext:扩展于ApplicationContext, 新增加两个主要方法。refresh()和close(),让ApplicationContext具有启动、刷新和关闭上下文的能力。ApplicationContext在初始化上下文时就实例化所有的单例Bean.

WebApplicationContext:WebApplicationContext是专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作。

最常用的两个实现类:

ClassPathXmlApplicationContext : 从类路径下加载配置文件。

FileSystemXmlApplicationContext : 从文件系统中加载配置文件。

两个都是继承ConfigurableApplicationContext

bean标记的使用?

1
<bean id="Bean 唯一标志符" class="包名+类名" p:普通属性="普通属性值" p:对象属性-ref="对象的引用">

Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置,如下表。

短命名空间简化的 XML 配置说明
p 命名空间元素中嵌套的元素是 setter 方式属性注入的一种快捷实现方式
c 命名空间元素中嵌套的元素是构造函数属性注入的一种快捷实现方式

bean的范围有哪些?

Spring 5 共提供了 6 种 scope 作用域,如下表。

作用范围描述
singleton默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例
prototype原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。
request每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。
session同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。
application同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。 与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。
websocketwebsocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。

如何获取bean?

  1. 在初始化时保存ApplicationContext对象
  2. 通过Spring提供的utils类获取ApplicationContext对象
  3. 继承自抽象类ApplicationObjectSupport
  4. 继承自抽象类WebApplicationObjectSupport
  5. 实现接口ApplicationContextAware
  6. 通过Spring提供的ContextLoader

bean的生命周期和生命周期方法?

在传统的 Java 应用中,Bean 的生命周期很简单,使用 Java 关键字 new 进行 Bean 的实例化后,这个 Bean 就可以使用了。一旦这个 Bean 长期不被使用,Java 自动进行垃圾回收。

相比之下,Spring 中 Bean 的生命周期较复杂,大致可以分为以下 5 个阶段:

  1. Bean 的实例化
  2. Bean 属性赋值
  3. Bean 的初始化
  4. Bean 的使用
  5. Bean 的销毁

Spring 根据 Bean 的作用域来选择 Bean 的管理方式,

  • 对于 singleton 作用域的 Bean 来说,Spring IoC 容器能够精确地控制 Bean 何时被创建、何时初始化完成以及何时被销毁;
  • 对于 prototype 作用域的 Bean 来说,Spring IoC 容器只负责创建,然后就将 Bean 的实例交给客户端代码管理,Spring IoC 容器将不再跟踪其生命周期。

Spring 生命周期流程
Bean 的生命周期回调方法主要有两种:

  • 初始化回调方法:在 Spring Bean 被初始化后调用,执行一些自定义的回调操作。
  • 销毁回调方法:在 Spring Bean 被销毁前调用,执行一些自定义的回调操作。

通过接口实现

1
2
3
4
5
6
7
8
9
10
11
12
//in bean class
@Override
public void afterPropertiesSet() throws Exception {
System.out
.println("【InitializingBean接口】调用InitializingBean.afterPropertiesSet()");
}

// 这是DiposibleBean接口方法
@Override
public void destroy() throws Exception {
System.out.println("【DiposibleBean接口】调用DiposibleBean.destory()");
}

通过XML配置实现

1
2
3
4
5
6
7
//in bean class
public void myInit() {
System.out.println("【init-method】调用<bean>的init-method属性指定的初始化方法");
}
public void myDestory() {
System.out.println("【destroy-method】调用<bean>的destroy-method属性指定的初始化方法");
}
1
2
3
<bean id="person" class="springBeanTest.Person" init-method="myInit"
destroy-method="myDestory" scope="singleton" p:name="张三" p:address="广州"
p:phone="15900000000" />

通过注解实现

注解描述
@PostConstruct指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。
@PreDestroy指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。

通过后置处理器实现

1
2
3
4
public interface BeanPostProcessor {   
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

两种显式装配?

构造函数注入

使用构造函数实现属性注入大致步骤如下:

  1. 在 Bean 中添加一个有参构造函数,构造函数内的每一个参数代表一个需要注入的属性;
  2. 在 Spring 的 XML 配置文件中,通过 <beans> 及其子元素 <bean> 对 Bean 进行定义;
  3. <bean> 元素内使用 <constructor-arg>元素,对构造函数内的属性进行赋值,Bean 的构造函数内有多少参数,就需要使用多少个 <constructor-arg> 元素。

setter注入

使用 setter 注入的方式进行属性注入,大致步骤如下:

  1. 在 Bean 中提供一个默认的无参构造函数(在没有其他带参构造函数的情况下,可省略),并为所有需要注入的属性提供一个 setXxx() 方法;
  2. 在 Spring 的 XML 配置文件中,使用 <beans> 及其子元素 <bean>对 Bean 进行定义;
  3. <bean>元素内使用 <property> 元素对各个属性进行赋值。

value和ref的区别?

value用于注入字面量属性,ref用于注入引用属性

如何注入集合?

标签说明用于注入 list 类型的值,允许重复用于注入 set 类型的值,不允许重复用于注入 key-value 的集合,其中 key 和 value 都可以是任意类型用于注入 key-value 的集合,其中 key 和 value 都是字符串类型

标签说明
<list>用于注入 list 类型的值,允许重复
<set>用于注入 set 类型的值,不允许重复
<map>用于注入 key-value 的集合,其中 key 和 value 都可以是任意类型
<props>用于注入 key-value 的集合,其中 key 和 value 都是字符串类型

构造函数注入?

1
2
3
4
<bean id="bean名" class="类">
<constructor-arg name="属性名" value="值"/>
<constructor-arg name="属性名" ref="引用"/>
</bean>

或使用c:命名空间

parent属性的作用?

在 Spring XML 配置中,我们通过子 Bean 的 parent 属性来指定需要继承的父 Bean,配置格式如下。

1
2
3
4
5
6
7
<!--父Bean-->
<bean id="parentBean" class="xxx.xxxx.xxx.ParentBean" >
<property name="xxx" value="xxx"/>
<property name="xxx" value="xxx"/>
</bean>
<!--子Bean-->
<bean id="childBean" class="xxx.xxx.xxx.ChildBean" parent="parentBean"/>

如何使用工厂方法实现DI?

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
public class AnimalFactory {

public static Animal getAnimal1() {
System.out.println("调用AnimalFactory类的静态工厂方法getAnimal1().................");
return new Dog();
}

public Animal getAnimal2() {
System.out.println("调用AnimalFactory类的实例工厂方法getAnimal2().................");
return new Pig();
}

private static Map<Integer,Animal> map;

static {
map=new HashMap<>();
map.put(1,new Dog());
map.put(2,new Pig());
map.put(3,new Sheep());
}

public static Animal getAnimal3(int type) {
System.out.println("调用AnimalFactory类的静态工厂方法getAnimal3().................");
return map.get(type);
}
}

使用静态工厂方法返回自身实例

1
2
3
4
5
<!-- 如果想通过一个工厂类的静态工厂方法创建bean
可通过factory-method属性指定创建实例的方法的名称
class指定工厂类的类型
-->
<bean class="com.qdu.bean.A" factory-method="getA" />

使用静态工厂方法返回其他类实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 如果希望调用A类的静态方法来获取B类的实例 -->
<!-- 这种情况下,class指定的应该是工厂方法所在的类的名称 -->
<!-- factory-method属性指定创建实例的方法的名称 -->
<!-- 实际创建的对象不是AnimalFactory类型,而是Dog类型 -->
<bean id="animal" class="com.qdu.bean.AnimalFactory"
factory-method="getAnimal1" />

<!-- 希望通过AnimalFactory这个工厂类的静态方法getAnimal3(int type) -->
<!-- 来创建要托给spring管理的Dog或Pig或Sheep的实例 -->
<bean id="animal" class="com.qdu.bean.AnimalFactory" factory-method="getAnimal3">
<!-- constructor-arg在构造函数注入的时候是构造函数参数的名称 -->
<!-- 但是对于工厂方法,就是指定一个普通方法参数的信息 -->
<constructor-arg name="type" value="2" />
</bean>

使用非静态工厂方法返回其他类实例(不考)

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 如果希望通过工厂类A的非静态方法来实例化B -->
<!-- 必须先有一个A的对象才可以 -->

<bean id="animalFactory" class="com.qdu.bean.AnimalFactory" />

<!-- 如果希望通过工厂类A的非静态方法来实例化B
这时候不再使用class属性来指定生成的bean的类型
factory-bean属性指定创建bean实例(如这里的Pig)的工厂对象是哪个,指定其id或name
factory-method属性指定创建bean实例的工厂方法的名称
id为animal的bean实际是一个Pig实例,也即是getAnimal2()方法返回的实例
-->
<bean id="animal" factory-bean="animalFactory" factory-method="getAnimal2" />

在java中获取bean

1
2
3
4
5
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("config/beans1.xml");
A a = ctx.getBean(A.class);
a.methodOfA();
}

自动装配的模式有哪些?

Spring 共提供了 5 中自动装配规则,它们分别与 autowire 属性的 5 个取值对应,具体说明如下表。

属性值说明
byName按名称自动装配。 Spring 会根据的 Java 类中对象属性的名称,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 id 或 name 属性值与这个对象属性的名称相同,则获取这个 Bean,并与当前的 Java 类 Bean 建立关联关系。
byType按类型自动装配。 Spring 会根据 Java 类中的对象属性的类型,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 class 属性值与这个对象属性的类型相匹配,则获取这个 Bean,并与当前的 Java 类的 Bean 建立关联关系。
constructor与 byType 模式相似,不同之处在与它应用于构造器参数(依赖项),如果在容器中没有找到与构造器参数类型一致的 Bean,那么将抛出异常。 其实就是根据构造器参数的数据类型,进行 byType 模式的自动装配。
default表示默认采用上一级元素设置的自动装配规则(default-autowire)进行装配。
no默认值,表示不使用自动装配,Bean 的依赖关系必须通过元素的 ref 属性来定义。

基于注解的自动装配

1
2
<!--开启组件扫描-->
<context:component-scan base-package=""/>
1
@Autowired

显式装配与自动装配的关系?

显式装配是在 XML 配置中通过中的 ref 属性,手动维护 Bean 与 Bean 之间的依赖关系的。

Spring 的自动装配功能可以让 Spring 容器依据某种规则(自动装配的规则,有五种),为指定的 Bean 从应用的上下文(AppplicationContext 容器)中查找它所依赖的 Bean,并自动建立 Bean 之间的依赖关系。而这一过程是在完全不使用任何元素 ref 属性的情况下进行的。

依赖注入使用哪些注解?

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

注解说明
@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller该注解通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

我们可以通过以下注解将定义好 Bean 装配到其它的 Bean 中。

注解说明
@Autowired可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。 @Autowired 注解默认按照 Bean 的类型进行装配,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false。如果我们想使用按照名称(byName)来装配,可以结合 @Qualifier 注解一起使用
@Resource作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。 @Resource 中有两个重要属性:name 和 type。 Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配;如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。
@Qualifier与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。

@Configuration和@Bean注解的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
@Configration 注解作用在类、接口(包含注解)上
@Configuration 用于定义配置类,可替换 xml 配置文件
@Configration 注解类中可以声明一个或多个 @Bean 方法
*/
@Configuration
public class MyConfig {
/*
@Bean 注解作用在方法上
@Bean 指示一个方法返回一个 Spring 容器管理的 Bean,也就是说方法返回值就是给Springr容器装配的bean
@Bean 一般和 @Component 或者 @Configuration 一起使用,也可以在 @Service 里使用,没有特定要求,主要看项目的需求。
@Bean 注解默认作用域为单例 singleton 作用域,可通过 @Scope(“prototype”) 设置为原型作用域
*/
@Bean
public MyBean myBean() {
return new MyBean();
}
@Bean
public MyBean myBean1() {
return new MyBean();
}
}

什么是AOP?

AOP 的全称是“Aspect Oriented Programming”,译为“面向切面编程”,和 OOP(面向对象编程)类似,它也是一种编程思想。

通知的概念?

AOP的一套术语

名称说明
Joinpoint(连接点)AOP 的核心概念,指的是程序执行期间明确定义的一个点,例如方法的调用、类初始化、对象实例化等。 在 Spring 中,连接点则指可以被动态代理拦截目标类的方法。
Pointcut(切入点)又称切点,指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。
Advice(通知)指拦截到 Joinpoint 之后要执行的代码,即对切入点增强的内容。
Target(目标)指代理的目标对象,通常也被称为被通知(advised)对象。
Weaving(织入)指把增强代码应用到目标对象上,生成代理对象的过程。
Proxy(代理)指生成的代理对象。
Aspect(切面)切面是切入点(Pointcut)和通知(Advice)的结合。

连接点的概念?

见上条

通知的类型(位置)?

共有5种:

通知说明
before(前置通知)通知方法在目标方法调用之前执行
after(后置通知)通知方法在目标方法返回或异常后调用
after-returning(返回后通知)通知方法会在目标方法返回后调用
after-throwing(抛出异常通知)通知方法会在目标方法抛出异常后调用
around(环绕通知)通知方法会将目标方法封装起来

用来创建通知的注解有哪些?

名称说明
@Aspect用于定义一个切面。
@Pointcut用于定义一个切入点。
@Before用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning用于定义后置通知,相当于 AfterReturningAdvice。
@Around用于定义环绕通知,相当于 MethodInterceptor。
@AfterThrowing用于定义抛出通知,相当于 ThrowAdvice。
@After用于定义最终通知,不管是否异常,该通知都会执行。
@DeclareParents用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。

如何声明切入点?

1
2
3
4
5
6
7
//Pointcut表示式
//我除了可以通过切入点表达式(execution)直接对切点进行定义外
//还可以通过切入点方法的名称来引用其他的切入点
//在使用方法名引用其他切入点时,还可以使用“&&”、“||”和“!”等表示“与”、“或”、“非”的含义
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
//定义为切点的方法,它的返回值类型必须为 void
private void log(){}

在返回后通知中如何获取目标方法的返回值?

1
2
3
4
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret) {
System.out.println("afterReturning advice ..."+ret);
}

XML如何配置AOP?

在 Spring 的 XML 配置文件中,添加以下内容启用 @AspectJ 注解支持。

1
2
3
4
<!-- 开启注解扫描 -->
<context:component-scan base-package=""/>
<!--开启AspectJ 自动代理-->
<aop:aspectj-autoproxy/>
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
<!-- 默认是不启用@AspectJ的支持,也就是默认情况下,@Aspect等注解是不起作用的,启用AspectJ支持后才会起作用 -->
<!-- 该注解的作用是启用@AspectJ风格的Spring AOP -->
<!-- 如果是基于xml schema的aop是否还需要使用该标记: 不用 -->
<!-- <aop:aspectj-autoproxy /> -->

<!-- 方面对应的类需要注册为spring管理的bean,才能将方面切入到需要的地方 -->
<bean id="logAspect1" class="com.qdu.aop.LogAspect1" />

<bean id="logAspect2" class="com.qdu.aop.LogAspect2" />

<bean class="com.qdu.service.impl.StudentServiceImpl" />

<bean class="com.qdu.service.impl.TeacherServiceImpl" />

<bean class="com.qdu.service.impl.MathServiceImpl" />

<!-- execution(* com.qdu.service.StudentService.*(..)) -->
<!-- execution(* com.qdu.service.MathService.add(..)) -->
<!-- execution(* com.qdu.service.MathService.divide(..)) -->
<!-- execution(* com.qdu.service.MathService.multiply(..)) -->

<!-- aop:config用于以xml格式配置aop -->
<aop:config>
<!-- aop:config标记中可以定义切入点,这样的切入点可以在多个aop:aspect标记中使用 -->
<aop:pointcut
expression="execution(* com.qdu.service.StudentService.*(..))"
id="pt1" />
<!-- aop:aspect用于配置一个方面对应的类 -->
<!-- ref指定方面类bean的id或name -->
<!-- order用于控制通知的执行顺序,值越小,该方面类中对应的通知就会先执行 -->
<aop:aspect ref="logAspect1" order="2">
<!-- 在aop:aspect标记内定义的切入点只能在该aop:aspect标记中使用 -->
<aop:pointcut
expression="execution(* com.qdu.service.MathService.add(..))" id="pt2" />
<aop:pointcut
expression="execution(* com.qdu.service.MathService.divide(..))" id="pt3" />
<aop:pointcut
expression="execution(* com.qdu.service.MathService.multiply(..))" id="pt4" />
<!-- aop:before用于配置前置通知,method指定作为前置通知的方法的名称 -->
<!-- pointcut属性用于指定切入点表达式,pointcut-ref用于指定引用的切入点的id -->
<aop:before method="before1" pointcut-ref="pt1" />
<aop:before method="before2" pointcut-ref="pt1" />
<!-- aop:after-returning用于配置返回后通知,returning用于指定一个参数名(可随便起,尽量有意义) -->
<!-- 这样可以在返回后通知对应的方法上添加一个该名称的参数,用于接收目标方法的返回值 -->
<aop:after-returning method="afterReturning" pointcut-ref="pt2"
returning="returnValue" />
<!-- aop:after-throwing用于配置抛出后通知,throwing属性指定一个参数名(可随便起,尽量有意义) -->
<!-- 这样可以抛出后通知对应的方法上添加一个该名称的参数,用于接收抛出的异常对象 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pt3" throwing="ex" />
<!-- aop:after用于配置最终通知/后置通知 -->
<aop:after method="after" pointcut-ref="pt3"/>
<!-- aop:around用于配置环绕通知 -->
<aop:around method="around" pointcut-ref="pt4"/>
</aop:aspect>
<aop:aspect ref="logAspect2" order="1">
<aop:before method="before3" pointcut-ref="pt1" />
<aop:before method="before4" pointcut-ref="pt1" />
</aop:aspect>
</aop:config>

JDBC Template的CRUD操作有哪些?

方法说明
public int update(String sql)用于执行新增、更新、删除等语句;sql:需要执行的 SQL 语句;args 表示需要传入到 SQL 语句中的参数。
public int update(String sql,Object… args)
public void execute(String sql)可以执行任意 SQL,一般用于执行 DDL 语句; sql:需要执行的 SQL 语句;action 表示执行完 SQL 语句后,要调用的函数。
public T execute(String sql, PreparedStatementCallback action)
publicListquery(String sql, RowMapperrowMapper, @Nullable Object… args)用于执行查询语句;sql:需要执行的 SQL 语句;rowMapper:用于确定返回的集合(List)的类型;args:表示需要传入到 SQL 语句的参数。
publicT queryForObject(String sql, RowMapperrowMapper, @Nullable Object… args)
public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes)用于批量执行新增、更新、删除等语句; sql:需要执行的 SQL 语句;argTypes:需要注入的 SQL 参数的 JDBC 类型;batchArgs:表示需要传入到 SQL 语句的参数。

如何创建处理全局异常?

局部异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//可以在控制器类中添加方法,用于处理异常,这个方法就叫做局部异常处理程序
//这样该方法可以处理这个控制器类里发生的异常
//异常处理程序使用@ExceptionHandler注解修饰,说明这个方法用于处理异常
//value属性用于指定处理的异常类型,多个类型使用一个数组给出
//在异常处理程序中,可以跳转到某个错误页面,返回一个字符串指定视图名称即可
//在异常处理程序中,可以使用Model等对象返回一些数据给页面
//如果希望获取异常信息,可以在方法上添加一个对应异常类型(如ArithmeticException)的参数
//也可使用Throwable来接收所有类型的异常对象
@ExceptionHandler({ArithmeticException.class,NumberFormatException.class})
public String handleException(Model model, Throwable ex) {
//可以考虑使用Log4J2等日志框架将异常记录到日志
logger.error("发生异常,异常消息: "+ex.getMessage());
//如果需要返回一些数据显示在页面,可以添加到Model对象中
model.addAttribute("msg", "局部异常处理程序,异常消息: "+ex.getMessage());
return "error"; //指定要跳转的视图的名称
}

全局异常

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
//全局异常处理程序所在的类也要成为spring mvc容器管理的bean
//@Controller、@Service、@Repository、@Component
//@RestController、@Configuration、@ControllerAdvice
//@ControllerAdvice也会将一个类注册为spring管理的bean
//@ControllerAdvice作用很多,其中一个作用是修饰包含全局异常处理程序类
//这样,需要开启对这个类所在的包的扫描,但是这里我们直接将类放到了com.qdu.controller的子包
//com.qdu.controller.advice包下,所以不需要额外开启包扫描
@ControllerAdvice
public class GlobalExceptionHandler {
private static Logger logger=LoggerFactory.getLogger(GlobalExceptionHandler.class);

//添加一个方法,作为异常处理程序
//方法需要使用@ExceptionHandler注解进行修饰
//可以考虑处理异常后,跳转到一个友好的错误页面,如果需要,也可在错误页面显示一些信息
//如果同时定义了全局异常处理和局部异常处理程序,那么会使用局部异常处理程序
@ExceptionHandler({ArithmeticException.class})
public String handleException(Model model, ArithmeticException e) {
logger.error("全局异常处理程序:程序发生异常,异常消息-"+e.getMessage());
model.addAttribute("msg", "全局异常处理程序,异常消息: "+e.getMessage());
return "error";
}

//异常处理程序前也可使用@ResponseBody注解,让返回的内容成为响应正文内容显示
//而不是要跳转的页面的名称
@ExceptionHandler({IOException.class,ArrayIndexOutOfBoundsException.class})
@ResponseBody
public String exceptionHandler2(Throwable ex) {
return "Exception occurred, exception message: "+ex.getMessage();
}
}

JDBC Template 和 NamedParameterJDBCTemplate的区别

JDBC Template:最基本的JDBC模板,支持基于索引参数(?)的查询

NamedParameterJdbcTemplate:使用该模板类执行查询的时候使用命名参数的方式绑定到SQL而不是索引参数

事务的特征?

事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。

  • 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。
  • 一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。
  • 隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。
  • 持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。

事务的传播行为?

事务传播行为(propagation behavior)指的是,当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。
Spring 提供了以下 7 种不同的事务传播行为。

名称说明
PROPAGATION_MANDATORY支持当前事务,如果不存在当前事务,则引发异常。
PROPAGATION_NESTED如果当前事务存在,则在嵌套事务中执行。
PROPAGATION_NEVER不支持当前事务,如果当前事务存在,则引发异常。
PROPAGATION_NOT_SUPPORTED不支持当前事务,始终以非事务方式执行。
PROPAGATION_REQUIRED默认传播行为,如果存在当前事务,则当前方法就在当前事务中运行,如果不存在,则创建一个新的事务,并在这个新建的事务中运行。
PROPAGATION_REQUIRES_NEW创建新事务,如果已经存在事务则暂停当前事务。
PROPAGATION_SUPPORTS支持当前事务,如果不存在事务,则以非事务方式执行。

事务并发可能导致的问题?

事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。

在实际应用中,经常会出现多个事务同时对同一数据执行不同操作,来实现各自的任务的情况。此时就有可能导致脏读、幻读以及不可重复读等问题的出现。

在理想情况下,事务之间是完全隔离的,这自然不会出现上述问题。但完全的事务隔离会导致性能问题,而且并不是所有的应用都需要事务的完全隔离,因此有时应用程序在事务隔离上也有一定的灵活性。

Spring 中提供了以下隔离级别,我们可以根据自身的需求自行选择合适的隔离级别。

方法说明
ISOLATION_DEFAULT使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读
ISOLATION_READ_COMMITTEDOracle 默认级别,允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读
ISOLATION_REPEATABLE_READMySQL 默认级别,多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读
ISOLATION_SERIALIZABLE完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读

PlatformTransactionManager接口的作用?

Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。

在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下。

1
2
3
4
5
6
7
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

void commit(TransactionStatus status) throws TransactionException;

void rollback(TransactionStatus status) throws TransactionException;
}

该接口中各方法说明如下:

名称说明
TransactionStatus getTransaction(TransactionDefinition definition)用于获取事务的状态信息
void commit(TransactionStatus status)用于提交事务
void rollback(TransactionStatus status)用于回滚事务

Spring 为不同的持久化框架或平台(例如 JDBC、Hibernate、JPA 以及 JTA 等)提供了不同的 PlatformTransactionManager 接口实现,这些实现类被称为事务管理器实现。

实现类说明
org.springframework.
jdbc.datasource.
DataSourceTransactionManager
使用 Spring JDBC 或 iBatis 进行持久化数据时使用。
org.springframework.
orm.hibernate3.
HibernateTransactionManager
使用 Hibernate 3.0 及以上版本进行持久化数据时使用。
org.springframework
.orm.jpa.
JpaTransactionManager
使用 JPA 进行持久化时使用。
org.springframework
.jdo.
JdoTransactionManager
当持久化机制是 Jdo 时使用。
org.springframework.
transaction.
jta.JtaTransactionManager
使用 JTA 来实现事务管理,在一个事务跨越多个不同的资源(即分布式事务)使用该实现。

这些事务管理器的使用方式十分简单,我们只要根据持久化框架(或平台)选用相应的事务管理器实现,即可实现对事物的管理,而不必关心实际事务实现到底是什么。

如何使用XML配置事务?

1. 引入 tx 命名空间

Spring 提供了一个 tx 命名空间,借助它可以极大地简化 Spring 中的声明式事务的配置。

想要使用 tx 命名空间,第一步就是要在 XML 配置文件中添加 tx 命名空间的约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

注意:由于 Spring 提供的声明式事务管理是依赖于 Spring AOP 实现的,因此我们在 XML 配置文件中还应该添加与 aop 命名空间相关的配置。

2. 配置事务管理器

接下来,我们就需要借助数据源配置,定义相应的事务管理器实现(PlatformTransactionManager 接口的实现类)的 Bean,配置内容如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库连接地址-->
<property name="url" value="xxx"/>
<!--数据库的用户名-->
<property name="username" value="xxx"/>
<!--数据库的密码-->
<property name="password" value="xxx"/>
<!--数据库驱动-->
<property name="driverClassName" value="xxx"/>
</bean>

<!--配置事务管理器,以 JDBC 为例-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

在以上配置中,配置的事务管理器实现为 DataSourceTransactionManager,即为 JDBC 和 iBatis 提供的 PlatformTransactionManager 接口实现。

3. 配置事务通知

在 Spring 的 XML 配置文件中配置事务通知,指定事务作用的方法以及所需的事务属性。

1
2
3
4
5
6
7
<!--配置通知-->
<tx:advice id="tx-advice" transaction-manager="transactionManager">
<!--配置事务参数-->
<tx:attributes>
<tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
</tx:attributes>
</tx:advice>
事务管理器配置

当我们使用 tx:advice 来声明事务时,需要通过 transaction-manager 参数来定义一个事务管理器,这个参数的取值默认为 transactionManager。

如果我们自己设置的事务管理器(第 2 步中设置的事务管理器 id)恰好与默认值相同,则可以省略对改参数的配置。

1
2
3
4
5
6
<tx:advice id="tx-advice" >
<!--配置事务参数-->
<tx:attributes>
<tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
</tx:attributes>
</tx:advice>

但如果我们自己设置的事务管理器 id 与默认值不同,则必须手动在 tx:advice 元素中通过 transaction-manager 参数指定。

事务属性配置

对于tx:advice 来说,事务属性是被定义在tx:attributes 中的,该元素可以包含一个或多个 tx:method 元素。

tx:method 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,如下表所示。

事务属性说明
propagation指定事务的传播行为。
isolation指定事务的隔离级别。
read-only指定是否为只读事务。
timeout表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。
rollback-for指定事务对于那些类型的异常应当回滚,而不提交。
no-rollback-for指定事务对于那些异常应当继续运行,而不回滚。

4. 配置切点切面

tx:advice 元素只是定义了一个 AOP 通知,它并不是一个完整的事务性切面。我们在 tx:advice 元素中并没有定义哪些 Bean 应该被通知,因此我们需要一个切点来做这件事。

在 Spring 的 XML 配置中,我们可以利用 Spring AOP 技术将事务通知(tx-advice)和切点配置到切面中,配置内容如下。

1
2
3
4
5
6
7
<!--配置切点和切面-->
<aop:config>
<!--配置切点-->
<aop:pointcut id="tx-pt" expression="execution(* net.biancheng.c.service.impl.OrderServiceImpl.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="tx-advice" pointcut-ref="tx-pt"></aop:advisor>
</aop:config>

如何启用注解驱动的事务编程模型?

1. 开启注解事务

tx 命名空间提供了一个 tx:annotation-driven 元素,用来开启注解事务,简化 Spring 声明式事务的 XML 配置。

tx:annotation-driven 元素的使用方式也十分的简单,我们只要在 Spring 的 XML 配置中添加这样一行配置即可。

1
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

tx:advice 元素一样,tx:annotation-driven 也需要通过 transaction-manager 属性来定义一个事务管理器,这个参数的取值默认为 transactionManager。如果我们使用的事务管理器的 id 与默认值相同,则可以省略对该属性的配置,形式如下。

1
<tx:annotation-driven/>

通过 tx:annotation-driven 元素开启注解事务后,Spring 会自动对容器中的 Bean 进行检查,找到使用 @Transactional 注解的 Bean,并为其提供事务支持。

2. 使用 @Transactional 注解

@Transactional 注解是 Spring 声明式事务编程的核心注解,该注解既可以在类上使用,也可以在方法上使用。

1
2
3
4
5
6
7
8
9
10
11
12
@Transactional
public class XXX {

@Transactional
public void A(Order order) {
……
}

public void B(Order order) {
……
}
}

若 @Transactional 注解在类上使用,则表示类中的所有方法都支持事务;若 @Transactional 注解在方法上使用,则表示当前方法支持事务。

Spring 在容器中查找所有使用了 @Transactional 注解的 Bean,并自动为它们添加事务通知,通知的事务属性则是通过 @Transactional 注解的属性来定义的。

@Transactional 注解包含多个属性,其中常用属性如下表。

事务属性说明
propagation指定事务的传播行为。
isolation指定事务的隔离级别。
read-only指定是否为只读事务。
timeout表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。
rollback-for指定事务对于那些类型的异常应当回滚,而不提交。
no-rollback-for指定事务对于那些异常应当继续运行,而不回滚。

SQLSessionFactory的创建与使用?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MybatisUtil {

private static SqlSessionFactory sqlSessionFactory;

static {
try {
InputStream is = Resources.getResourceAsStream("config/mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
sqlSessionFactory = builder.build(is);
} catch (IOException ex) {
ex.printStackTrace();
}
}

public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}

public static SqlSession openSession(){
return sqlSessionFactory.openSession();
}
}

如何使用Java类替换web.xml?

web.xml对应的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

// spring的配置
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { SpringConfig.class };
}

// springMVC的配置
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { SpringMvcConfig.class };
}

// servlet配置
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}

// servlet过滤器
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new CharacterEncodingFilter("utf-8",true,true)
};
}
}

springApplication.xml相应的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Configuration
//开启对service组件所在包的扫描
@ComponentScan(basePackages= {"com.qdu.service"})
//指定要使用的属性文件的位置,src目录下的文件要添加classpath:作为前缀
@PropertySource({"classpath:config/jdbc.properties"})
//开启对Mapper接口所在包的扫描,这样mybatis-spring可以帮你发现映射器
//帮你创建Mapper接口的对象,也就是映射器实例
@MapperScan(basePackages= {"com.qdu.mapper"})
//启用注解驱动的编程模型,这样可以通过使用@Transactional注解来实现事务
@EnableTransactionManagement
public class SpringConfig {

//注入一个Environment对象,用于读取属性文件中的属性
@Autowired
Environment env;

//1. 配置数据源
@Bean
public DruidDataSource dataSource() {

//属性文件中属性的值都是字符串,所以如果需要转换类型,需要手动转换
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driver"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));

dataSource.setInitialSize(Integer.parseInt(env.getProperty("initialSize")));
dataSource.setMaxActive(Integer.parseInt(env.getProperty("maxActive")));
dataSource.setMaxWait(Integer.parseInt(env.getProperty("maxWait")));
dataSource.setMinIdle(Integer.parseInt(env.getProperty("minIdle")));
dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(env.getProperty("timeBetweenEvictionRunsMillis")));
dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(env.getProperty("minEvictableIdleTimeMillis")));

dataSource.setValidationQuery(env.getProperty("validationQuery"));
dataSource.setTestWhileIdle(Boolean.parseBoolean(env.getProperty("minEvictableIdleTimeMillis")));
dataSource.setTestOnBorrow(Boolean.parseBoolean(env.getProperty("testOnBorrow")));
dataSource.setTestOnReturn(Boolean.parseBoolean(env.getProperty("testOnReturn")));

return dataSource;
}

//2. 配置SqlSessionFactory
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
//通过SqlSessionFactoryBean来创建SqlSessionFactory对象
SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean();
//用传入的DataSource对象设置为依赖的数据源
factoryBean.setDataSource(ds);
//指定要使用别名的类型所在的包,这样在mybatis映射文件等文件中可以使用类型的别名
factoryBean.setTypeAliasesPackage("com.qdu.entity");
//返回创建的SqlSessionFactory对象,成为spring容器管理的bean
return factoryBean.getObject();
}

//3. 配置事务管理器
@Bean
public DataSourceTransactionManager txManager(DataSource ds) {
return new DataSourceTransactionManager(ds);
}
}

springMVC.xml对应的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Configuration
@ComponentScan(basePackages = {"com.qdu.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index").setViewName("index");
}

@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCacheable(false);
templateResolver.setCharacterEncoding("UTF-8");
return templateResolver;
}

@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}

@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setCharacterEncoding("UTF-8");
return viewResolver;
}
}

如何集成SSM?

java配置类配置ssm

见上条

xml配置ssm

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring-config.xml</param-value>
</context-param>

<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>

spring-config

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<beans >

<!-- 1. 开启包扫描 -->
<!-- 因为后面使用MapperScannerConfigurer这个类的配置开启了对Mapper接口所在包的扫描 -->
<!-- 而且我们已经没有Mapper的实现类了,所以这里不需要扫描mapper包了 -->
<context:component-scan base-package="com.qdu.service" />

<!-- 2. 指定要加载的属性文件的位置,可以指定多个,逗号隔开 -->
<context:property-placeholder location="classpath:config/jdbc.properties" />

<!-- 3. 配置数据源,这里使用alibaba的DruidDataSource -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">

<property name="driverClassName" value="${jdbc.driverClass}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />

<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}" />
<property name="maxWait" value="${maxWait}" />
<property name="minIdle" value="${minIdle}" />

<property name="timeBetweenEvictionRunsMillis"
value="${timeBetweenEvictionRunsMillis}" />
<property name="minEvictableIdleTimeMillis"
value="${minEvictableIdleTimeMillis}" />

<property name="validationQuery" value="${validationQuery}" />
<property name="testWhileIdle" value="${testWhileIdle}" />
<property name="testOnBorrow" value="${testOnBorrow}" />
<property name="testOnReturn" value="${testOnReturn}" />
</bean>

<!-- 4. 配置SqlSessionFactoryBean,告知spring如何创建SqlSessionFactory对象 -->
<!-- 并将创建的SqlSessionFactory交给spring容器管理,然后可以在需要的地方依赖注入 -->

<!-- 这里class指定的SqlSessionFactoryBean,这是一个工厂bean,负责生产某种类型的对象 -->
<!-- 它负责创建SqlSessionFactory对象并交给spring管理该对象 -->
<!-- 这里的id是它创建的SqlSessionFactory这个bean的id,而不是SqlSessionFactoryBean的id -->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 如果还需要使用mybatis配置文件,可以使用configLocation属性指定其位置 -->
<!-- <property name="configLocation" value="classpath:mybatis-config.xml" /> -->
<!-- typeAliasesPackage属性用于指定实体类所在的包,这样在mybatis映射文件中可以使用别名来使用实体类 -->
<property name="typeAliasesPackage" value="com.qdu.entity" />
<!-- mapperLocations属性用于指定mybatis映射文件的位置 -->
<!-- <property name="mapperLocations" value="classpath:com/qdu/mapper/**/*.xml" /> -->
</bean>

<!-- 5. 配置MapperScannerConfigurer,扫描Mapper接口所在的包,这样可以发现映射器 -->

<!-- 可以不用一个个注册映射器,而是直接指定Mapper接口所在的包 -->
<!-- 让spring扫描Mapper接口所在的包,根据每个Mapper接口创建对应的Mapper对象,也就是映射器实例 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 需要使用basePackage属性指定Mapper接口所在的包
这样映射器会自动被创建,在需要的地方直接注入映射器(Mapper实例)即可-->
<property name="basePackage" value="com.qdu.mapper" />
</bean>

<!-- 6. spring集成mybatis应该将事务交给spring管理 -->

<!-- 6.1 配置事务管理器,事务管理器提供方法操作事务 -->

<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 事务管理器和SqlSessionFactoryBean使用的数据源必须是同一个 -->
<property name="dataSource" ref="dataSource" />
</bean>

<!-- 6.2 配置事务通知 -->
<!-- 配置事务通知其实就是配置通知的bean,但是不使用bean标记 -->
<!-- 而是使用tx:advice标记简化事务配置,需要指定通知bean的id和依赖的事务管理器-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- tx:method标记用于指定每个要加事务的方法如何应用事务 -->
<!-- 查询操作可加事务,可不加事务,但是如果查询操作可能受并发事务影响 -->
<!-- 那就要考虑加事务 -->
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="update*" />
<tx:method name="delete*" />
<tx:method name="transfer*" />
<!-- 如果查询加了事务,最好使用只读事务,方便优化查询 -->
<tx:method name="get*" read-only="true" />
</tx:attributes>
</tx:advice>

<!-- 6.3 配置事务通知器,指定事务通知应用到什么切入点 -->
<aop:config>
<aop:pointcut expression="within(com.qdu.service..*)" id="pt"/>
<!-- 配置通知器,指定哪个通知应用到哪个切入点 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
</aop:config>

</beans>

spring-mvc

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
<?xml version="1.0" encoding="UTF-8"?>
<beans >
<!--1. 开启包扫描,扫描控制器所在包 -->
<context:component-scan base-package="com.qdu.controller" />
<!--2. 启用注解驱动的控制器编程模型,也就是启用Web MVC配置 -->
<mvc:annotation-driven />
<!--3. 配置Thymeleaf相关的bean -->
<!-- 配置模板解析器 -->
<bean id="templateResolver"
class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/" />
<property name="suffix" value=".html" />
<!-- templateMode指定Thymeleaf处理的模板类型 -->
<property name="templateMode" value="HTML" />
<!-- cacheable指定是否可以缓存模板页面内容
开发的时候设置为false,实际发布程序可以设置为true
设置true的话,页面被缓存,如果更改了页面内容,刷新页面不会改变
设置false的话,如果更改了页面内容,刷新页面内容会更改
-->
<property name="cacheable" value="false" />
<property name="characterEncoding" value="UTF-8" />
</bean>
<!-- 配置模板引擎 -->
<bean id="templateEngine"
class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
<!-- enableSpringELCompiler属性设置为true是为了提高性能 -->
<!-- 只有考虑向后兼容的时候才会设置为false -->
<property name="enableSpringELCompiler" value="true" />
</bean>
<!-- 配置Thymeleaf视图解析器 -->
<bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<property name="characterEncoding" value="UTF-8" />
</bean>
<!-- 4. 处理静态资源 -->
<mvc:resources mapping="/static/**" location="/static/" />

<!-- 5. 配置跳转到首页 -->
<mvc:view-controller path="/" view-name="index" />
<mvc:view-controller path="/index" view-name="index" />
</beans>

如何配置Spring Security?

1.添加启动依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.配置spring security

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
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

//注入要使用的UserDetailsService对象
@Autowired
private CustomUserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//指定要使用UserDetailsService对象,从而实现从数据库加载用户信息(用户名、密码和角色/授权)
//并指定要使用的密码加密器
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}

@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**");
}

@Override
protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()
.antMatchers("/", "/index", "/toLogin", "/login_failed").permitAll()
.antMatchers("/user/**").hasRole("user")
.antMatchers("/admin/**").hasAnyRole("admin", "sadmin")
.antMatchers("/sadmin/**").hasRole("sadmin")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/toLogin")
.failureUrl("/login_failed")
.and()
.rememberMe()
.rememberMeCookieName("remember")
.rememberMeParameter("rememberMe")
.tokenValiditySeconds(7 * 24 * 60 * 60)
.and()
.httpBasic()
.and()
.csrf().disable();


// http.httpBasic();
// http.csrf().disable();
}
}

3.自定义security功能

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
//如果使用spring security验证用户,用户的信息来自数据库
//可以创建一个UserDetailsService类来指定如何加载用户信息
//需要将其注册为spring管理的bean,在需要的地方依赖注入即可
@Component
public class CustomUserDetailsService implements UserDetailsService{

@Autowired
private UserInfoMapper mapper;

//loadUserByUsername()方法中查询用户信息,封装成验证要用的用户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//如果需要spring security进行用户的验证和授权
//spring security需要知道用户的用户名、密码和角色
//这里根据传入的用户名(直接使用即可,会帮你传入输入的用户名)
//根据用户名查询用户的信息,主要是为了获取密码
UserInfo userinfo=mapper.selectByPrimaryKey(username);

//如果查不到该用户,抛出一个用户名找不到异常
if(null==userinfo)
throw new UsernameNotFoundException("无此用户");

//根据用户名查询用户所属的角色列表
List<String> roles=mapper.selectRoleNamesById(username);

//用户角色列表需要封装成一个List<GrantedAuthority>列表
List<GrantedAuthority> authorities=new ArrayList<>();
//遍历每个角色名称,将每个角色名称封装成一个GrantedAuthority对象
//添加到列表中,表示对用户的一项授权,如果多个角色,则有多项授权
for(String role:roles) {
//角色名必须要添加ROLE_前缀,这是spring security的工作机制决定的
authorities.add(new SimpleGrantedAuthority("ROLE_"+role));
}
//最终要返回一个UserDetails对象,封装了验证和授权用的用户名、密码和授权信息
//UserDetails是一个接口,可以使用其实现类User
return new User(userinfo.getUid(),userinfo.getUpassword(),authorities);
}
}

spring MVC 12问

MVC模式的概念?

MVC 设计模式一般指 MVC 框架,M(Model)指数据模型层,V(View)指视图层,C(Controller)指控制层。使用 MVC 的目的是将 M 和 V 的实现代码分离,使同一个程序可以有不同的表现形式。其中,View 的定义比较清晰,就是用户界面。

Spring MVC包含的组件及其作用?

1)DispatcherServlet

DispatcherServlet 是前端控制器,从图 1 可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。

2)HandlerMapping

HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。

3)HandlerAdapter

HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。

4)Handler

Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。

5)View Resolver

View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)。

6)View

View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。

以上组件中,需要开发人员进行开发的是处理器(Handler,常称Controller)和视图(View)。通俗的说,要开发处理该请求的具体代码逻辑,以及最终展示给用户的界面。

Spring MVC处理请求的过程?

Spring MVC执行流程

SpringMVC 的执行流程如下。

  1. 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);
  2. 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。
  3. DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
  4. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
  5. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
  6. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
  7. DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
  8. ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
  9. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
  10. 视图负责将结果显示到浏览器(客户端)。

servlet合法的URL模式?

servlet的url模式合法的写法有:

  • 精准匹配
    • ./index.jsp
  • 扩展名匹配
    • *.xxx 匹配以xxx结尾的请求url
  • 路径匹配
    • /xxx/* 用于匹配路径包含xxx的所有请求,xxx可以是一级或者多级
    • /* 匹配以/开头,任意结尾的请求url。
  • 缺省匹配
    • / 匹配除JSP之外的所有请求

如何配置DispatcherServlet?

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
<!--web.xml-->
<web-app>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 如果不指定<context-param>, 将会默认加载/WEB-INF/applicationContext.xml文件.-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>

<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>

Java配置:实现WebApplicationInitializer接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyWebApplicationInitializer implements WebApplicationInitializer {

@Override
public void onStartup(ServletContext servletCxt) {

// 加载spring 容器
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();

// 创建并注册DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}

如何配置Spring MVC配置文件的位置?

Spring MVC 配置:在 web.xml 中配置 Servlet,创建 Spring MVC 的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 配置 SpringMVC 的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置 DispatcherServlet 的一个初始化参数:spring mvc 配置文件按的位置和名称-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--作为框架的核心组件,在启动过程中有大量的初始化操作要做
而这些操作放在第一次请求时才执行会严重影响访问速度
因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时-->
<load-on-startup>1</load-on-startup>
</servlet>

Spring和Spring MVC配置文件的默认名称?

springMVC配置文件默认名称:<servlet-name>-servlet.xml
servlet-name是在web.xml<servlet>标签中定义的
spring配置文件名称:applicationContext.xml

如何配置视图解析器?

in *-.servlet

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
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

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

<!-- 配置 Thymeleaf 视图解析器 -->
<bean id="viewResolver"
class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
</beans>

视图解析器(ViewResolver)是 Spring MVC 的重要组成部分,负责将逻辑视图名解析为具体的视图对象。

Spring MVC 提供了很多视图解析类,其中每一项都对应 Java Web 应用中特定的某些视图技术。下面介绍一些常用的视图解析类。

URLBasedViewResolver

UrlBasedViewResolver 是对 ViewResolver 的一种简单实现,主要提供了一种拼接 URL 的方式来解析视图。

UrlBasedViewResolver 通过 prefix 属性指定前缀,suffix 属性指定后缀。当 ModelAndView 对象返回具体的 View 名称时,它会将前缀 prefix 和后缀 suffix 与具体的视图名称拼接,得到一个视图资源文件的具体加载路径,从而加载真正的视图文件并反馈给用户。

使用 UrlBasedViewResolver 除了要配置前缀和后缀属性之外,还需要配置“viewClass”,表示解析成哪种视图。示例代码如下

1
2
3
4
5
6
7
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">            
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/> <!--不能省略-->
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>

InternalResourceViewResolver

InternalResourceViewResolver 为“内部资源视图解析器”,是日常开发中最常用的视图解析器类型。它是 URLBasedViewResolver 的子类,拥有 URLBasedViewResolver 的一切特性。

InternalResourceViewResolver 能自动将返回的视图名称解析为 InternalResourceView 类型的对象。InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。也就是说,使用 InternalResourceViewResolver 视图解析时,无需再单独指定 viewClass 属性。示例代码如下。

1
2
3
4
5
6
7
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/> <!--可以省略-->
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>

创建和使用控制器?

@Controller 注解用于声明某类的实例是一个控制器。例如,创建控制器类 IndexController,示例代码如下

1
2
3
4
@Controller
public class IndexController {
// 处理请求的方法
}

Spring MVC 使用扫描机制找到应用中所有基于注解的控制器类,所以,为了让控制器类被 Spring MVC 框架扫描到,需要在配置文件中声明 spring-context,并使用 <context:component-scan/> 元素指定控制器类的基本包(请确保所有控制器类都在基本包及其子包下)。

例如,在 springmvcDemo 应用的配置文件 springmvc-servlet.xml 中添加以下代码:

1
2
<!-- 使用扫描机制扫描控制器类,控制器类都在net.biancheng.controller包及其子包下 -->
<context:component-scan base-package="com.example.controller" />

如何映射不同的请求?

在基于注解的控制器类中可以为每个请求编写对应的处理方法。使用 @RequestMapping 注解将请求与处理方法一 一对应即可。

@RequestMapping 注解可用于类或方法上。用于类上,表示类中的所有响应请求的方法都以该地址作为父路径。

@RequestMapping 注解常用属性如下。

1. value 属性

value 属性是 @RequestMapping 注解的默认属性,因此如果只有 value 属性时,可以省略该属性名,如果有其它属性,则必须写上 value 属性名称。如下。

1
2
3
@RequestMapping(value="toUser")
//or
@RequestMapping("toUser")

value 属性支持通配符匹配,如 @RequestMapping(value="toUser/*") 表示 http://localhost:8080/toUser/1http://localhost:8080/toUser/hahaha 都能够正常访问。

2. path属性

path 属性和 value 属性都用来作为映射使用。即 @RequestMapping(value="toUser")@RequestMapping(path="toUser") 都能访问 toUser() 方法。

path 属性支持通配符匹配,如 @RequestMapping(path="toUser/*") 表示 http://localhost:8080/toUser/1http://localhost:8080/toUser/hahaha 都能够正常访问。

3. name属性

name属性相当于方法的注释,使方法更易理解。如 @RequestMapping(value = "toUser",name = "获取用户信息")

4. method属性

method 属性用于表示该方法支持哪些 HTTP 请求。如果省略 method 属性,则说明该方法支持全部的 HTTP 请求。

@RequestMapping(value = "toUser",method = RequestMethod.GET)表示该方法只支持 GET 请求。

也可指定多个 HTTP 请求,如 @RequestMapping(value = "toUser",method = {RequestMethod.GET,RequestMethod.POST}),说明该方法同时支持 GET 和 POST 请求。

5. params属性

params 属性用于指定请求中规定的参数,代码如下。

1
2
3
4
@RequestMapping(value = "toUser",params = "type")
public String toUser() {
return "showUser";
}

以上代码表示请求中必须包含 type 参数时才能执行该请求。即 http://localhost:8080/toUser?type=xxx 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser 则不能正常访问 toUser() 方法。

1
2
3
4
@RequestMapping(value = "toUser",params = "type=1")
public String toUser() {
return "showUser";
}

以上代码表示请求中必须包含 type 参数,且 type 参数为 1 时才能够执行该请求。即 http://localhost:8080/toUser?type=1 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser?type=2 则不能正常访问 toUser() 方法。

6. header属性

header 属性表示请求中必须包含某些指定的 header 值。

@RequestMapping(value = "toUser",headers = "Referer=http://www.xxx.com") 表示请求的 header 中必须包含了指定的“Referer”请求头,以及值为“http://www.xxx.com”时,才能执行该请求。

7. consumers属性

consumers 属性用于指定处理请求的提交内容类型(Content-Type),例如:application/json、text/html。如
@RequestMapping(value = "toUser",consumes = "application/json")

8. produces属性

produces 属性用于指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型。如 @RequestMapping(value = “toUser”,produces = “application/json”)。

除此之外,produces 属性还可以指定返回值的编码。如 @RequestMapping(value = "toUser",produces = "application/json,charset=utf-8"),表示返回 utf-8 编码。

使用 @RequestMapping 来完成映射,具体包括 4 个方面的信息项:请求 URL、请求参数、请求方法和请求头。

如何配置静态资源?

1
2
3
4
5
6
<mvc:resources mapping="/js/**" location="/js/"></mvc:resources>
<mvc:resources mapping="/css/**" location="/css/"></mvc:resources>
<mvc:resources mapping="/img/**" location="/img/"></mvc:resources>
<!-- 在配置了mvc:resources标签之后必须配置mvc:annotation-driven标签静态资源
才可以访问,否则不仅静态资源不能访问,其他的所有请求也都无法正常处理了 -->
<mvc:annotation-driven/>

如何构建Restful API?

REST(Representational State Transfer)即表述性转移,是目前最流行的一种软件架构风格。它结构清晰、易于理解、有较好的扩展性。

Spring REST 风格可以简单理解为:使用 URL 表示资源时,每个资源都用一个独一无二的 URL 来表示,并使用 HTTP 方法表示操作,即准确描述服务器对资源的处理动作(GET、POST、PUT、DELETE),实现资源的增删改查。

  • GET:表示获取资源
  • POST:表示新建资源
  • PUT:表示更新资源
  • DELETE:表示删除资源

Spring Boot 65问

框架22问

启动依赖是什么?

Spring Boot就可以指定基于功能依赖。Spring Boot通过起步依赖为项目的依赖管理提供帮助。如果应用程序是Web应用程序(功能),不需要向项目pom.xml文件中添加一堆单独的依赖,可以直接向项目中添加Web起步依赖。如果应用程序需要用到JPA持久化(功能),加入jpa起步依赖;如果需要安全功能(功能),就加入security起步依赖。添加依赖时不需要指定依赖的版本号,依赖的版本号由当前是使用的Spring Boot版本号来决定。

起步依赖就是特殊的Maven依赖,利用了传递依赖解析,把常用库聚合在一起,组成几个为特定功能而定制的依赖。Spring Boot通过起步依赖:直接引入相关起步依赖就行,我们不需要考虑支持某种功能需要什么库, 减少了依赖数量,而且不需要考虑这些库的那些版本。如果我们需要什么功能,就往项目中加入该功能的起步依赖就好了。

Actuator的作用?

Actuator用于监视和管理应用程序

监控的内容:

  • Spring应用程序上下文中配置的Bean
  • Spring Boot的自动配置做的决策
  • 应用程序可用的环境变量、系统属性、配置属性和命令行参数等
  • 应用程序里线程的当前状态
  • 应用程序最近处理的HTTP请求的追踪情况
  • 各种和内存使用、垃圾回收、Web请求等相关的指标

Spring Boot 配置依赖?

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

如何修饰启动类?

1
2
3
4
5
6
7
8
9
10
<!--排除依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
</exclusions>
</dependency>
1
2
3
4
5
6
<!--用指定依赖版本 覆盖 传递依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.3</version>
</dependency>

@SpringBootApplication是哪三合一?

@SpringBootApplication = (默认属性)@Configuration + @EnableAutoConfiguration + @ComponentScan。

@Configuration:建一个简单的spring配置类,可以用来替代相应的xml配置文件,见词条 @Configuration和@Bean注解的作用

@EnableAutoConfiguration:能够自动配置spring的上下文,试图猜测和配置你想要的bean类,通常会自动根据你的类路径和你的bean定义自动配置

@ComponentScan:会自动扫描指定包下的全部标有@Component的类,并注册成bean,当然包括@Component下的子注解@Service,@Repository,@Controller

如何排除指定的自动配置?

1
@SpringBootApplication(exclude={RedisAutoConfiguration.class})

一个启动类能否同时作为控制器类?

如何自定义banner?

banner.txt(或banner,jpg)放入src/main/resources

SpringBoot如何实现自动配置?

加载spring.factories

SpringBoot如何配置视图解析器?

配置文件

1
2
spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.html

1
2
3
4
5
spring:
mvc:
view:
prefix: /pages/
suffix: .html

配置类

1
2
3
4
5
6
7
8
9
10
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Bean
public InternalResourceViewResolver configureInternalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/pages/");
resolver.setSuffix(".html");
return resolver;
}
}

如何配置端口号?

1
server.port=8081

1
2
server:
port: 8081

properties与yml的异同?

properties的优先级会高于yml

yml采用树形结构,更有层次感,可读性很强;相反,properties 则更为直接

properties 的基本语法格式是“key=value”的形式;yml 的基本语法格式是“key: value”的形式,冒号后面需要加空格

静态资源默认位置在哪?

classpath:/META-INF/resources/

classpath:/resources/

classpath:/static/

classpath:/public/

如何自定义静态资源的位置?

在配置文件中配置

1
2
3
4
#静态资源访问路径
spring.mvc.static-path-pattern=/upload/**
#静态资源映射路径
spring.resources.static-locations=classpath:/upload/

配置类

1
2
3
4
5
6
7
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {     // 这里之所以多了一"/",是为了解决打war时访问不到问题
registry.addResourceHandler("/**").addResourceLocations("/","classpath:/");
}
}

有哪些常用的JSON解析器?

1)json-lib

json-lib 最早也是应用广泛的 JSON 解析工具,缺点是依赖很多的第三方包

对于复杂类型的转换,json-lib 在将 JSON 转换成 Bean 时还有缺陷,比如一个类里包含另一个类的 List 或者 Map 集合,json-lib 从 JSON 到 Bean 的转换就会出现问题。

2)开源的Jackson

开源的 Jackson 是 Spring MVC 内置的 JSON 转换工具。

但是 Jackson 对于复杂类型的 JSON 转换 Bean 会出现问题

3)Google的Gson

Gson 是目前功能最全的 JSON 解析神器

Gson 完全可以将复杂类型的 JSON 到 Bean 或 Bean 到 JSON 的转换,是 JSON 解析的神器。Gson 在功能上面无可挑剔,但性能比 FastJson 有所差距。

4)阿里巴巴的FastJson

FastJson 是用 Java 语言编写的高性能 JSON 处理器,由阿里巴巴公司开发。

FastJson 在复杂类型的 Bean 转换 JSON 上会出现一些问题,可能会出现引用的类型,导致 JSON 转换出错,需要制定引用。

为哪些JSON解析器提供了自动配置?

Gson 、Jackson 、JSON-B

如何使用fastjson?

导入依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.15</version>
</dependency>

在springMVC的配置类中配置JSON解析器为fastJSON

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
//通过配置HttpMessageConverter来配置使用的JSON处理器
//每个JSON处理器都有对应的消息转换器实现类,用于实现java对象和json对象的转换
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

FastJsonHttpMessageConverter converter=new FastJsonHttpMessageConverter();
//设置转换器的字符集编码,解决中文乱码

converter.setDefaultCharset(Charset.forName("UTF-8"));
//因为spring boot没有提供fastjson的集成和自动配置,所以不能在application.properties
//中配置fastjson的属性

//创建一个FastJsonConfig对象,定制fastjson的配置
FastJsonConfig config=new FastJsonConfig();
config.setDateFormat("yyyy-MM-dd");
//setSerializerFeatures()用于指定序列化后json数据的特征
//WriteMapNullValue: 是否输出值为null的字段,默认为false
//WriteNullStringAsEmpty: null字符串是否显示为空字符串,而不是null
//WriteNullNumberAsZero: 是否将null的数值显示为0,而不是null
//WriteNullListAsEmpty: 是否将null的List显示为[],而不是null
//DisableCircularReferenceDetect: 禁用对同一个对象的循环引用,如果使用Hibernate等框架映射了关系,可能会出现循环引用
config.setSerializerFeatures(
SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteNullStringAsEmpty,
SerializerFeature.WriteNullNumberAsZero,
SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.DisableCircularReferenceDetect
);

//应用fastjson配置
converter.setFastJsonConfig(config);

converters.add(converter);

for(HttpMessageConverter c:converters)
System.out.println(c.getClass()); //打印一下转换器的类型
}

直接用。。。

如何自定义错误界面的位置?

静态

在 resource/static 目录下创建 error 目录,然后在 error 目录中创建错误展示页面。错误展示页面命名规则有如下两种:

一种是直接使用具体的响应码命名文件,比如:404.html、405.html、500.html
另一种可以使用 4xx.html、5xx.html 这样的命名

动态

在 resource/templates 目录下创建 error 目录,然后在 error 目录中创建错误展示页面。

错误界面使用的顺序如何?

优先级:

动态错误码>静态错误码>动态模糊错误码>静态模糊错误码

找到一个后便不往下寻找

如何定义局部异常handler?

1
2
3
4
5
6
7
@ExceptionHandler(value = {java.lang.ArithmeticException.class})
public ModelAndView arithmeticExceptionHandler(Exception e){
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg",e);
mv.setViewName("error");
return mv;
}

如何定义全局异常handler?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//创建一个全局异常处理程序
//全局异常处理程序所在的类使用@ControllerAdvice或@RestControllerAdvice注解修饰
//@ControllerAdvice注解作用很多,其中一个作用是修饰全局异常处理程序所在的类
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler({ ArithmeticException.class })
public String handleException(Exception ex, Model model) {
// 在此收集异常信息,比如将异常信息写入日志等
model.addAttribute("msg", "全局异常处理程序,异常消息:"+ex.getMessage());
return "err"; // 处理完异常,可以跳转到指定的页面/视图
}

//异常处理程序对应的方法前可以使用@ResponseBody注解,将返回值作为响应内容返回
@ExceptionHandler({ArrayIndexOutOfBoundsException.class})
@ResponseBody
public Map<String,Object> handleException2(Exception e) {
Map<String,Object> map=new HashMap<>();
map.put("status", 500);
map.put("msg", "数组索引出界,索引: "+e.getMessage());
map.put("reason", "索引值超出数组长度");
return map;
}
}

如何使用RestController?

如果在类上加上@RestController,该类中的所有SpringMVCUrl接口映射都是返回json格式

1
2
3
4
@RestController
public class hello{
//something
}

Thymeleaf 11问

Thymeleaf如何使用变量表达式页面获取不同范围的属性?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
变量表达式${.....}
<p>
<!-- 能够直接获取model的属性值-->
user1属性值:
<span th:text="${user1.id}"></span> |
<span th:text="${user1.password}" ></span> |
<span th:text="${user1.gender}" ></span>
</p>
<p>
user2属性值:
<!-- 会话范围的属性需要添加session的使用 -->
<span th:text="${session.user2.id}"></span> |
<span th:text="${session.user2.password}" ></span> |
<span th:text="${session.user2.gender}" ></span>
</p>

Thymeleaf的五种表达式?

  • 变量表达式${…..}
  • 选择变量表达式*{…..}
    • 选择变量表达式主要是为了简化书写
    • th:object属性用于绑定一个对象,这样在当前元素(p元素)内可以直接使用该对象的属性
    • 选择变量表达式用于使用外围绑定的对象的属性,以简化书写
  • 消息表达式#{…..}
    • 消息表达式用于显示消息,主要是来自属性文件的属性,可用于实现国际化等功能
    • {属性名}来显示来自属性文件的一个消息
  • URL链接变量表达式@{…..}
    • 用于指定一个链接,指定一个资源的url
  • 片段(fragment)表达式~{…..}
    • 页面名称::片段名称

如何使用选择变量表达式?

1
2
3
4
5
6
<p th:object="${user1}">
user1属性值:
<span th:text="*{id}"></span> |
<span th:text="*{password}" ></span> |
<span th:text="*{gender}" ></span>
</p>

链接表达式的四种写法?

1
2
3
4
5
6
7
8
9
10
11
12
<div class="content">
<a th:href="@{https://www.baidu.com}" class="btn btn-sm btn-danger">测试1-绝对路径</a> &nbsp;
<!-- 请求/页面相对路径:直接写资源的路径 -->
<!-- 使用相对路径,要注意404问题 -->
<a th:href="@{static/img/lxh01.gif}" class="btn btn-sm btn-warning">测试2-页面/请求相对路径</a> &nbsp;
<!-- 上下文相对路径,以/开头,表示相对程序的上下文路径 -->
<a th:href="@{/static/img/lxh02.gif}" class="btn btn-sm btn-success">测试3-上下文相对路径</a> &nbsp;
<!-- 服务器相对路径,以~/开头,表示相对服务器的路径 -->
<a th:href="@{~/static/img/lxh03.gif}" class="btn btn-sm btn-primary">测试4-服务器相对路径</a> &nbsp;
<!-- 协议相对路径: 以//开头,表示相对协议的路径 -->
<a th:href="@{//static/img/lxh04.gif}" class="btn btn-sm btn-info">测试5-协议相对路径</a> &nbsp;
</div>

怎么定义和引用片段?

定义

1
2
3
4
5
<div th:fragment="f1" id="frag1" class="bg-primary padding10 margin10">
这是模板片段1内容~~~~~~~~~~~~~~~~~~~<br>
这是模板片段1内容~~~~~~~~~~~~~~~~~~~<br>
这是模板片段1内容~~~~~~~~~~~~~~~~~~~<br>
</div>

引用

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="content">
<!-- th:insert用于插入一个模板片段,比如下面的代码表示将f1片段内容插入div1 -->
<!-- 页面名称::片段名称 -->
<div id="div1" th:insert="~{footer::f1}"></div>
<!-- th:replace用于使用指定的模板片段替换原有内容,比如下面的代码表示使用f1的片段内容替换div2 -->
<div id="div2" th:replace="~{footer::f1}"></div>
<!-- 也可通过页面名称::#id通过元素的id来使用一个片段 -->
<div id="div3" th:insert="~{footer::#frag2}"></div>
<div id="div4" th:insert="~{footer}"></div>
<!-- 使用当前页面定义的片段,可不加页面名称或使用this -->
<div id="div5" th:insert="~{::frag3}"></div>
<div id="div5" th:insert="~{this::#fragment3}"></div>
</div>

哪些字面标记不合法?

字面标记值类似文本字面值: 区别是不使用单引号括起来值,
只能允许字母、数字、下划线、中划线、点号、中括号,
不能包含逗号、空格以及其他特殊字符

1
2
3
4
5
6
7
8
字面标记:<span th:text="Anna"></span><br>
字面标记:<span th:text="Annabelle_Lee"></span><br>
字面标记:<span th:text="www.baidu.com"></span><br>
字面标记:<span th:text="a-b_c.d"></span><br>
<!-- 空格不允许 -->
<!-- 字面标记:<span th:text="[kite runner]"></span><br> -->
<!-- 逗号不允许 -->
<!-- 字面标记:<span th:text="a,b"></span><br>

th:*属性的作用?

th:* 修改单个属性的值

1
2
3
4
5
6
7
8
9
10
11
<!-- 绝大部分html属性都有对应的th:*属性,在th:*属性中才能使用Thymeleaf的语法 -->
<div class="content">
<p>
<!--显示user5的头像,鼠标悬浮显示其id-->
<img th:src="'static/img/'+${user5.img}" th:title="${user5.id}"
src="static/img/lxh01.gif" title="小胖" width="60" height="60">

<img th:src="@{'/static/img/'+${user5.img}}" th:title="${user5.id}"
src="static/img/lxh01.gif" title="小胖" width="60" height="60">
</p>
</div>

th:attr 修改一个标签多个属性的值

1
2
3
4
5
6
7
<div class="content">
<p>
<!--显示user5的头像,鼠标悬浮显示其id-->
<img th:attr="src=@{'/static/img/'+${user5.img}},title=${user5.id}"
src="static/img/lxh01.gif" title="小胖" width="60" height="60">
</p>
</div>

th:object 绑定一个对象,方便在子标记中使用选择变量表达式

1
2
3
4
5
6
7
8
9
<div class="content">
<p th:object="${user5}">
user5属性值:
<span th:text="*{id}"></span> |
<span th:text="*{password}"></span> |
<span th:text="*{gender}"></span> |
<span th:text="*{img}"></span>
</p>
</div>

th:if 判断一个条件,条件成立,显示标记内容

1
2
3
<div class="content">
<p th:if="${age}>=18">恭喜你,成为大人了!!!</p>
</div>

th:switch+th:case 类似switch-case结构作用的属性

1
2
3
4
5
6
7
8
9
10
<div class="content">
<!-- th:switch通常用于判断一个表达式的值 -->
<div th:switch="${type}">
<p th:case="1">冰箱</p>
<p th:case="2">空调</p>
<p th:case="3">电视</p>
<!-- *表示其他情况 -->
<p th:case="*">其他</p>
</div>
</div>

th:each 类似foreach循环

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
<div class="content">
<table class="table table-striped table-hover text-center">
<tr>
<th>学号</th>
<th>姓名</th>
<th>性别</th>
</tr>
<!--1)遍历列表-->
<tr th:each="s:${studentList}">
<!-- th:属性名写法 -->
<td th:text="${s.sid}"></td>
<!-- data-th-属性名写法 -->
<td data-th-text="${s.sname}"></td>
<!-- 内联表达式写法 -->
<td>[[${s.sgender}]]</td>
</tr>
</table>
<!--2)获取状态信息-->
<!-- s是一个变量名,可以随意命名,表示当前遍历的元素 -->
<!-- stat是一个变量名,可以随意命名,用于获取当前遍历的元素的状态信息 -->
<div th:each="s,stat:${studentList}" th:class="${stat.odd}?'bg-danger':'bg-success'">
[[${s.sid}]] | [[${s.sname}]] |
<!-- index:获取当前元素的索引,从0开始 -->
[[${stat.index}]] |
<!-- count: 获取当前元素的序号,也就是第几个元素 -->
[[${stat.count}]] |
<!-- size: 获取集合大小 -->
[[${stat.size}]] |
<!-- first: 返回布尔值,表示是否是第一项元素 -->
[[${stat.first}]] |
<!-- last: 返回布尔值,表示是否是最后一项元素 -->
[[${stat.last}]] |
<!-- odd: 返回布尔值,表示是否是奇数项元素 -->
[[${stat.odd}]] |
<!-- even: 返回布尔值,表示是否是偶数项元素 -->
[[${stat.even}]] |
</div>
<!--3)遍历Map集合-->
<hr>
<!-- 遍历Map集合,通过key获取键,通过value获取值 -->
<div th:each="entry,status:${map1}">
[[${entry.key}]] | [[${entry.value}]] | [[${status.index}]]
</div>
<!-- 遍历map2集合,打印键和值的各项信息 -->
<!-- value如果不是简单类型,可继续获取具体属性 -->
</div>

th:text和th:utext

1
2
3
4
5
6
7
8
9
10
<div class="content">
<!-- th:text如果内容是html内容,则不会解析html内容,作为文本显示 -->
<p th:text="${content1}"></p>
<!--th:text等效写法:内联表达式写法-->
[[${content1}]]
<!-- th:utext如果内容是html内容,会解析html内容,显示解析后的内容 -->
<p th:utext="${content2}"></p>
<!--th:utext等效写法:内联表达式写法-->
[(${content2})]
</div>

th:with 用于定义局部变量

1
2
3
4
5
6
7
8
9
<!-- th:with用于定义局部变量,可以定义一个或多个局部变量 -->
<!-- 定义的变量只能在标记内使用 -->
<div th:with="firstStudent=${studentList[0]}">
[[${firstStudent.sid}]] | [[${firstStudent.sname}]] | [[${firstStudent.sgender}]]
</div>
<!--定义多个变量-->
<div th:with="a=10,b=20,c=30">
[[${a+b+c}]]
</div>

th:inline 启用和禁用内联表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div th:inline="none">
[[1+2+3]]
<br>
[(1+2+3)]
</div>
<div>
<!-- 如果需要在js代码中使用内联表达式,可将inline属性设置为javascript -->
<script th:inline="javascript">
document.write([[${user5.id}]]);
document.write("<br>");
document.write([[${age}]]);
</script>
</div>
<div>
<!-- 默认,可以在css样式中使用内联表达式 -->
<!-- 如果设置值是none,css中的内联表达式不会被解析 -->
<style th:text="css">
.bg-red{
color: [[${color}]];
}
</style>
<p class="bg-red">测试样式</p>
</div>

Thymeleaf的名称空间?

Thymeleaf提供一些名称空间,用于获取对应的信息。每个名称空间都对应一个Map集合,存储了对应的属性、参数或其他信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="content">
<!-- 不使用任何名称空间就是请求范围的属性 -->
<!--获取请求范围属性-->
<p th:text="${num1}"></p>
<p th:text="${num2}"></p>
<!--获取会话范围属性-->
<p th:text="${session.num3}"></p>
<!--获取应用程序范围属性-->
<p th:text="${application.num4}"></p>
<!--获取一个请求参数的单个值-->
<p th:text="${param.name}"></p>
<!--获取一个请求参数的多个值-->
<p th:text="${param.hobbies}"></p>
<!--获取一个请求参数的多个值中的某个值,加上数组索引即可-->
<p th:text="${param.hobbies[0]}"></p>
</div>

有哪些基本对象?

Thymeleaf 中常用的内置基本对象如下:

  • ctx :上下文对象
  • vars :上下文变量
  • locale:上下文的语言环境
  • request:HttpServletRequest 对象(仅在 Web 应用中可用)
  • response:HttpServletResponse 对象(仅在 Web 应用中可用)
  • session:HttpSession 对象(仅在 Web 应用中可用)
  • servletContext:ServletContext 对象(仅在 Web 应用中可用)

表达式工具对象如何使用?

numbers:数字工具对象

1
2
3
4
5
formatDecimal()方法: 用于格式化小数数值
<div class="content">
<!-- 这里的三个参数分别表示:要格式化的值,至少保留几位整数,保留几位小数 -->
<p th:text="${#numbers.formatDecimal(num1,1,3)}"></p>
</div>

formatCurrency()方法:用于格式化金额,会加货币符号

1
<p th:text="${#numbers.formatCurrency(num1)}"></p>

sequence()方法: 用于产生一个整数序列/数组, 可以通过产生一个整数序列实现普通循环,如打印分页的页码

1
2
3
4
5
    <!-- 可以指定两个参数,表示from和to,最小值和最大值 -->
<!-- 可以指定三个参数,表示from、to和step,最小值、最大值和步长 -->
<span th:each="i:${#numbers.sequence(1,10)}">
| <a th:href="'findByPageNo?pageNo='+${i}">[[${i}]]</a> |
</span>

strings:字符串工具对象

1
2
3
4
5
6
7
8
9
10
11
<div class="content">
<!-- 检查一个字符串内容是否为空 -->
<p th:text="${#strings.isEmpty('')}"></p>
<p th:text="${#strings.isEmpty('anna')}"></p>
<!-- 获取字符串长度 -->
<p th:text="${#strings.length(bookName)}"></p>
<!-- 截取子字符串 从索引为0的字符开始,截取5-0个字符-->
<p th:text="${#strings.substring(bookName,0,5)}"></p>
<!-- 检查字符串是否包含指定内容 -->
<p th:text="${#strings.contains(bookName,'kite')}"></p>
</div>

dates:日期工具对象

1
2
3
4
5
6
7
8
9
10
11
<!-- 格式化日期时间 -->
<p th:text="${#dates.format(now)}"></p>
<!-- 格式化的时候可指定显示格式 -->
<p th:text="${#dates.format(now,'yyyy-MM-dd HH:mm:ss D E')}"></p>
<!-- 获取日期时间的指定部分, 如年份、月份、日、月份名称、星期几等 -->
<p th:text="${#dates.year(now)}"></p>
<p th:text="${#dates.month(now)}"></p>
<p th:text="${#dates.day(now)}"></p>
<p th:text="${#dates.monthName(now)}"></p>
<p th:text="${#dates.dayOfWeek(now)}"></p>
<p th:text="${#dates.hour(now)}"></p>

lists/sets:List/Set 集合工具对象

1
2
3
4
5
6
7
<div class="content">
<!-- #lists对象用于操作List类型的数据,比如sort()方法可用于对集合排序-->
<span th:each="name:${#lists.sort(nameList)}">
[[${name}]] |
</span>
<p th:text="${#lists.sort(scoreList)}"></p>
</div>

aggregates

1
2
3
4
5
<!-- #aggregates用于对数组或集合进行汇总运算,如求和或求平均 -->
<div class="content">
<p th:text="${#aggregates.avg(scoreList)}"></p>
<p th:text="${#aggregates.sum(scoreList)}"></p>
</div>

maps:Map 集合工具对象

常用的方法有:size、isEmpty、containsKey 和 containsValue 等

Spring Boot JPA 13问

SpringBoot支持的三种数据源是什么?

HikariCP:默认内置数据源对象

Tomcat提供DataSource:HikariCP不可用的情况下,在web环境中,将tomcat服务器配置的数据源对象。

Commons DBCP:HikariCP不可用,tomcat数据源也不可用,将使用dbcp数据源。

如何配置数据源?

导入依赖项

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8
username: root
password: root
jpa:
hibernate:
ddl-auto: update
show-sql: true

SpringDataJPA是什么?

spring data jpa是spring提供的一套简化JPA开发的框架,按照约定好的【方法命名规则】写dao层接口,就可以在不写接口实现的情况下,实现对数据库的访问和操作。同时提供了很多除了CRUD之外的功能,如分页、排序、复杂查询等等。

SpringData JPA 需不需要JPA提供程序?

需要

Spring Data JPA 可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现。

JPA个各个属性都有什么作用?

1
2
3
4
5
6
7
8
# 指定是否在控制台打印生成的sql语句,以方便跟踪程序执行和调试程序,默认值为false
spring.jpa.show-sql=true
# 指定是否格式化在控制台打印的sq|语句
spring.jpa.properties.hibernate.format_sql=true
# 指定使用的方言,是一个类的名称,根据操作的数据库选择
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57InnoDBDialect
# 指定程序运行时是否自动生成、更新或删除数据库表
spring.jpa.hibernate.ddl-auto=update

ddl-auto的有效值有:

  • create: 每次加载hibernate时,先删除已存在的数据库表结构再重新生成
  • create-drop:每次加载hibernate时,先删除已存在的数据库表结构再重新生成,并且当sessionFactory关闭时自动删除生成的数据库表结构:
  • update: 只在第一次加载hibernate时自动生成数据库表结构,以后再次加载hibernate时根据 model类自动更新表结构
  • validate:每次加载hibermate时,验证数据库表结构,仅仅和数据库中的表进行比较,不会 创建新表,但是会插入新值
  • none:禁用DDL操作

有哪些常用的注解?

  • @Entity定义对象将会成为被JPA管理的实体,将映射到指定的数据库表。
  • @Table指定数据库的表名。
  • @Id定义属性为数据库的主键,一个实体里面必须有一个。
  • @IdClass利用外部类的联合主键。
  • @GeneratedValue为主键生成策略
  • @Basic表示属性是到数据库表的字段的映射。如果实体的字段上没有任何注解,默认即为@Basic。
  • @Transient表示该属性并非一个到数据库表的字段的映射,表示非持久化属性,与@Basic作用相反。JPA映射数据库的时候忽略它。
  • @Column定义该属性对应数据库中的列名。
  • @Temporal用来设置Date类型的属性映射到对应精度的字段。
  • @Lob 将属性映射成数据库支持的大对象类型,支持以下两种数据库类型的字段。

关联关系注解

  • @JoinColumn定义外键关联的字段名称
  • @OneToOne关联关系
  • @OneToMany与@ManyToOne可以相对存在,也可只存在一方。
  • @ManyToMany表示多对多,和@OneToOne、@ManyToOne一样也有单向、双向之分。单向双向和注解没有关系,只看实体类之间是否相互引用。

Repository接口和他的派生类型?

Repository: 是 spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法
仅仅是一个标识,表明任何继承它的均为仓库接口类,方便Spring自动扫描识别

CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法

PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法

JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法

如何构建简单的条件查询?

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
//resposity

List<Student> findByUserDepUuid(String uuid);

//框架在解析该方法时,流程如下:
/*
首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Doc
先判断 userDepUuid(根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,
如果是,则表示根据该属性进行查询;如果没有该属性,继续往下走

从右往左截取第一个大写字母开头的字符串(此处为Uuid),
然后检查剩下的字符串是否为查询实体的一个属性,如果是,
则表示根据该属性进行查询;如果没有该属性,则重复这一步,继续从右往左截取;
最后假设 user 为查询实体的一个属性
接着处理剩下部分(DepUuid),
先判断 user 所对应的类型是否有depUuid属性,
如果有,则表示该方法最终是根据 "Doc.user.depUuid"
的取值进行查询;否则继续按照步骤3的规则从右往左截取,
最终表示根据 "Doc.user.dep.uuid" 的值进行查询。
*/

//以下是条件查询中可用的关键字

//5. 构建条件查询-根据性别和班级查询学生列表
//如果多个参数,则参数顺序和方法名称要对应起来,比如这里第一个参数必须是班级
List<Student> findBySbatchAndSgender(String batchName, String gender);

//6. 构建条件查询-根据学号或姓名查询学生
List<Student> findBySidOrSname(String id, String name);

//7. 构建条件查询-根据两个学号查询学号介于该区间的学生的列表
List<Student> findBySidBetween(String start, String end);

//8. 构建条件查询-查询学号小于指定学号的学生列表
List<Student> findBySidLessThan(String id);

//9. 构建条件查询-查询学号小于等于指定学号的学生列表
List<Student> findBySidLessThanEqual(String id);

//10. 构建条件查询-查询学号大于指定学号的学生列表
List<Student> findBySidGreaterThan(String id);

//11. 构建条件查询-查询学号大于等于指定学号的学生列表
List<Student> findBySidGreaterThanEqual(String id);

//12. 构建条件查询-查询班级为null的学生的列表
List<Student> findBySbatchNull();

//13. 构建条件查询-查询学号包含指定关键字的学生列表
List<Student> findBySidLike(String id);

//14. 构建条件查询-查询名字包含指定关键字的学生列表
List<Student> findBySnameLike(String name);

//15. 构建条件查询-查询名字以指定字符串结尾的学生列表
List<Student> findBySnameEndingWith(String name);
1
2
3
4
5
6
7
8
9
10
//server
//根据主键查找查找时
public Product get(Long id) {
//findById()返回的是一个Optional对象,需要进一步调用get()返回实际数据对象
return repo.findById(id).get();
}
@Override
public List<Student> getStudentListByUuid(String Uuid) {
return studentRepository.findByUuid(Uuid);
}

如何使用@Query查询?

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
// 1. 查询id最大的员工的信息
// 使用@Query注解定义JPQL查询
// JPQL(Java Persistence Querying Language-Java持久化语言)或者叫做JPA Querying
// Language-操作的是对象和对象的属性
// Native Query-本地/原生SQL语句-操作的是表和表中的列

// select emp相当于查询员工对象的所有属性,emp.属性名 表示查询对应的属性
@Query("select emp from Employee emp where id=(select max(id) from Employee)")
Employee query1();

// 2. 根据性别和部门编号查询员工列表
// 使用占位符参数 ?X 表示查询参数
// Employee类中通过dept属性映射了关系,但是Employee类中没有deptId属性
// 所以如果需要使用deptId属性,需要写dept.deptId
@Query("select emp from Employee emp where gender=?1 and dept.deptId=?2")
List<Employee> query2(String gender, Integer deptId);

// 3. 根据性别和部门编号查询员工列表
// 使用命名参数 :参数名
// 可通过@Param注解指定查询参数的名称
@Query("select emp from Employee emp where gender=:gen and dept.deptId=:did")
List<Employee> query3(@Param("gen") String gender, @Param("did") Integer deptId);

// 4. 查询姓名包含指定关键字和指定性别的员工
// Spring Data支持在占位符参数两端添加%的使用
@Query("select emp from Employee emp where name like %?1% and gender=?2")
List<Employee> query4(String name, String gender);

// 5. 查询姓名包含指定关键字和指定性别的员工
// Spring Data支持在命名参数两端添加%的使用
@Query("select emp from Employee emp where name like %:name% and gender=:gender")
List<Employee> query5(@Param("name") String name, @Param("gender") String gender);

// 6. 查询所有员工
// JPQL: 操作的是对象和对象的属性
// 原生SQL: 操作的是表和表中的列
// 使用本地查询/原生SQL语句,需要设置nativeQuery属性为true
// Spring Data JPA不仅仅支持JPQL语句,也支持原生SQL语句
@Query(value = "select * from emp", nativeQuery = true)
List<Employee> query6();

// 7. 联接表查询指定列
// 查询指定列,可以考虑将每行数据封装到一个Object[]数组
@Query("select e.id,e.name,e.gender,d.deptId,d.deptName from Employee e, Department d where e.dept.deptId=d.deptId")
List<Object[]> query7();

如何进行增删改?

直接调用JPA给定的 CRUD 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void addStudent(Student student) {
studentRepository.save(student);
}

@Override
public void updateStudent(Student student) {
studentRepository.save(student);
}

@Override
public void deleteStudent(String id) {
studentRepository.deleteById(id);
}

或者在repository层中定义Query

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 8. 修改指定员工的姓名
// 需要使用@Modifying注解和@Transactional注解
// @Query("select emp from Employee emp")
// 如果执行的是一个增删改操作,则必须添加@Modifying和@Transactional注解的使用
// @Transactional注解建议加到Service层的方法上,有时候一个Service功能可能涉及多个Dao的调用,需要作为一个事务执行
@Modifying
@Query("update Employee set name=:name where id=:id")
void update1(@Param("id") Integer id, @Param("name") String name);

// 9. 添加一个新员工
// 需要使用@Modifying注解和@Transactional注解
// JPQL不支持insert语句
@Modifying
@Query(value = "insert into emp(name,gender,dept_id) values(:name,:gender,:deptId)", nativeQuery = true)
void insert(@Param("name") String name, @Param("gender") String gender, @Param("deptId") Integer deptId);

// 10. 刪除一个员工
// 需要使用@Modifying注解和@Transactional注解
@Modifying
@Query("delete Employee where id=?1")
void delete(Integer id);

如何声明事务?

1
2
3
4
5
//例
@Transactional(propagation=Propagation.SUPPORTS, readOnly=true)
public List<PayInfo> findAll() {
return payInfoDao.findAll();
}
属性类型描述
valueString可选的限定描述符,指定使用的事务管理器
propagationenum: Propagation可选的事务传播行为设置
isolationenum: Isolation可选的事务隔离级别设置
readOnlyboolean读写或只读事务,默认读写
timeoutint (in seconds granularity)事务超时时间设置
rollbackForClass对象数组,必须继承自Throwable导致事务回滚的异常类数组
rollbackForClassName类名数组,必须继承自Throwable导致事务回滚的异常类名字数组
noRollbackForClass对象数组,必须继承自Throwable不会导致事务回滚的异常类数组
noRollbackForClassName类名数组,必须继承自Throwable不会导致事务回滚的异常类名字数组

如何配置mapper映射文件的位置?

在配置文件中

1
mybatis.mapper-locations=classpath:com/qdu/mapper/**/*.xml

如何开启对mapper包的扫描?

在启动类中

1
@MapperScan("com.qdu.mapper")

消息传递 10问

消息传递的概念?

消息传递是一个或多个实体之间进行通信的一-种方式,它无处不在。自计算机发明以来,以一种或
另一种形式进行的计算机消息传递就已经存在。它被定义为硬件和/或软件组件或应用程序之间的通
信方法。总会有一个发送者和一个或多个接收者。消息传递可以是同步和异步,发布/订阅, RPC
基于企业的消息传递,ESB (企业服务总线),MOM (面向消息的中间件)等等。

JMS的概念?

JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。

点对点模型有什么特点?

  1. 一个消息由一个消息生产者(producer)传送给一个消息使用者(consumer)
  2. 消息由生产者发送到队列(Queue)目的地,然后传送给在该队列上注册了的消息使用者之一
  3. 任意多数量的消息生产者都可以发送消息同一个队列,每条消息都可以确保被传送成功,且每条消息仅由一个消息使用者收到和使用
  4. 如果没有消息使用者注册接受队列中的消息,则队列保留该消息,直到有使用者读取该消息,一旦读取,该消息便不在队列中,不可再有其他使用者读取。

发布订阅模型的特点?

  1. 一个消息由一个消息生产者(Producer/Publisher)传送给任意数量的消息使用者(Consumer/Subscriber)
  2. 消息由生产者发送到主题(Topic)目的地, 然后由订阅了该主题的活跃消息使用者读取和使用
  3. 任意数量的消息生产者可以发送消息到主题目的地,每条消息传送给任意数量订阅了该主题的消息使用者
  4. 如果没有订阅该主题的消息使用者,则Topic目的地不会保留该消息(除非有非活跃使用者进行了持久性订阅),后续订阅该主题的消息使用者也不会再收到使用者之前发的消息。
  5. 一个持久性订阅表示注册了该主题的使用者可以在生产者发送消息的时候处于非活跃状态,这样,使用者可以在变成活跃状态时收到之前发送的消息。

ActiveMQ是什么?

ActiveMQ是一种开源的基于JMS(Java Message Servie)规范的一种消息中间件的实现,ActiveMQ的设计目标是提供标准的、面向消息的、能够跨越多语言和多系统的应用集成消息通信中间件。

它为企业应用中消息传递提供高可用、出色性能、可扩展、稳定和安全保障。

ActiveMQ实现JMS规范并在此之上提供大量额外的特性。ActiveMQ支持队列和订阅两种模式的消息发送。

如何启动ActiveMQ?

1
2
cd <activeMQ_dir>/bin
./activemq start

JMS创建什么链接实例?

javax.jms.Connection

(由于使用SpringBoot,此处不再赘述)

如何配置ActiveMQ?

首先配置properties文件

1
2
3
4
5
6
7
8
9
10
11
12
#activemq通讯地址
spring.activemq.broker-url=tcp://localhost:61616
#用户名
spring.activemq.user=admin
#密码
spring.activemq.password=admin
#是否启用内存模型(就是不安装mq,项目启动时同时启动一个mq实例)
spring.activemq.in-memory=false
#信任所有包
spring.activemq.packages.trust-all=true
#是否替换默认的连接池,使用activemq的连接池需引入依赖
spring.activemq.pool.enabled=false

然后配置activeMQ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
@EnableJms
public class ActiveMQConfig {
@Bean
public Queue queue() {
return new ActiveMQQueue("springboot.queue") ;
}

//springboot默认只配置queue类型消息,如果要使用topic类型的消息,则需要配置该bean
@Bean
public JmsListenerContainerFactory jmsTopicListenerContainerFactory(ConnectionFactory connectionFactory){
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//这里必须设置为true,false则表示是queue类型
factory.setPubSubDomain(true);
return factory;
}

@Bean
public Topic topic() {
return new ActiveMQTopic("springboot.topic") ;
}
}

或者在properties中配置

1
2
# 使用点对点模型
spring.jms.pub-sub-domain=false

配置文件的其他配置:

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
# 是否信任所有包
spring.activemq.packages.trust-all=
# 要信任的特定包的逗号分隔列表(当不信任所有包时)
spring.activemq.packages.trusted=
# 当连接请求和池满时是否阻塞。设置false会抛“JMSException异常”。
spring.activemq.pool.block-if-full=true
# 如果池仍然满,则在抛出异常前阻塞时间。
spring.activemq.pool.block-if-full-timeout=-1ms
# 是否在启动时创建连接。可以在启动时用于加热池。
spring.activemq.pool.create-connection-on-startup=true
# 是否用Pooledconnectionfactory代替普通的ConnectionFactory。
spring.activemq.pool.enabled=false
# 连接过期超时。
spring.activemq.pool.expiry-timeout=0ms
# 连接空闲超时
spring.activemq.pool.idle-timeout=30s
# 连接池最大连接数
spring.activemq.pool.max-connections=1
# 每个连接的有效会话的最大数目。
spring.activemq.pool.maximum-active-session-per-connection=500
# 当有"JMSException"时尝试重新连接
spring.activemq.pool.reconnect-on-exception=true
# 在空闲连接清除线程之间运行的时间。当为负数时,没有空闲连接驱逐线程运行。
spring.activemq.pool.time-between-expiration-check=-1ms
# 是否只使用一个MessageProducer
spring.activemq.pool.use-anonymous-producers=true

@JMSListener有什么用?

1
2
3
4
5
6
//该注解用于将一个方法设置为监听端点,用于接收消息
//destination用于指定接收消息的目的地,也就是消息来自哪个目的地,给出其名称
@JmsListener(destination="queue2")
public void receiveMsg1(String msg) {
System.out.println("监听端点1收到消息,消息内容:"+msg);
}

如何使用发布订阅模型?

1
2
# 设置为ture使用发布订阅模型, i.e
spring.jms.pub-sub-domain=true

测试 9问

单元测试与集成测试?

软件工程第十一章单元测试与集成测试部分

Junit5的三个组件是什么?

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: 是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
  • JUnit Jupiter: 提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。
  • JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,其提供了兼容JUnit4.x,Junit3.x的测试引擎。

Junit的常用注解?

@Test :表示方法是测试方法

1
2
3
@Test
void contextLoads() {
}

@DisplayName :为测试类或者测试方法设置展示名称

1
2
3
4
5
@DisplayName("测试displayname注解")
@Test
void testDisplayName() {
System.out.println(1);
}

@BeforeEach:表示在每个单元测试之前执行

@AfterEach:表示在每个单元测试之后执行

@BeforeAll:表示在所有单元测试之前执行

@AfterAll:表示在所有单元测试之后执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@BeforeEach
void testBeforeEach() {
System.out.println("测试就要开始。。。");
}

@AfterEach
void testAfterEach() {
System.out.println("测试就要结束。。。");
}

@BeforeAll
static void testBeforeAll() {
System.out.println("所有测试就要开始。。。");
}

@AfterAll
static void testAfterAll() {
System.out.println("所有测试已经结束。。。");
}

@Disabled :表示测试类或测试方法不执行

1
2
3
4
5
6
@Disabled
@Test
void test2() {
System.out.println(2);
System.out.println(jdbcTemplate.getClass());
}

@Timeout :表示测试方法运行如果超过了指定时间将会返回错误

1
2
3
4
5
@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void testTimeOut() throws InterruptedException {
Thread.sleep(520);
}

@RepeatedTest :表示方法可重复执行

1
2
3
4
5
6
7
@RepeatedTest(5)    //重复测试5次
@DisplayName("测试3")
@Test
void test3() {
System.out.println(3);
System.out.println(jdbcTemplate.getClass());
}

@ParameterizedTest :表示方法是参数化测试

@Tag :表示单元测试类别

@ExtendWith :为测试类或测试方法提供扩展类引用

Junit的测试方法与注意事项?

就这么测。。。

注意注意就行了。。。

Test依赖包含的库有哪些?

img.png

如何测试Spring Boot程序?

@SpringBootTest注解:添加在需要依赖spring boot框架的测试类上, 不然不能使用Spring boot的相关开发功能
@Test注解:添加在测试方法上

1
2
3
4
5
6
@SpringBootTest
class Boot05WebAdminApplicationTests {
@Test
void contextLoads() {
}
}

MockMvc是什么?

MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。

@WebMvcTest有什么用?

1
2
//该注解的使用实现针对spring mvc组件的切片测试,这样只加载mvc相关的配置的即可
@WebMvcTest(controllers= {HelloController.class})

如果使用@SpringBootTest来修饰一个测试类,
则会加载所有配置,并注册相关的bean成为spring管理的bean,
包括service组件、mapper组件、数据源等

但是如果测试只涉及部分组件,如控制器,不需要使用service或mapper等组件,
可以使用切片测试对应的注解来实施测试,这样只加载需要的配置即可

比如使用@WebMvcTest,实现对spring mvc组件的切片测试,
这样就不能注入service等组件,因为不会加载service等的配置


SSM & SpringBoot 138问138答
2023/02/07/subject/ssm/
作者
charlesix59
发布于
2023年2月7日
许可协议