Java面试题-SSM框架
Posted IT-熊猫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试题-SSM框架相关的知识,希望对你有一定的参考价值。
SSM常见面试题
文章目录
三个框架:Mybatis、Spring、Spring MVC
Mybatis
https://mybatis.org/mybatis-3/zh/index.html
(1)什么是Mybatis?
1)Mybatis是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL语句本身,不需要花费精力去处理加载驱动.创建连接,创建 preparedstatement等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql执行性能,灵活度高。
2)MyBatis可以使用 XML或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC代码和手动设置参数以及获取结果集。
3)通过 xml文件或注解的方式将要执行的各种 statement配置起来,并通过 java对象和 statement中 sql的动态参数进行映射生成最终执行的 sql语句,最后由 mybatis框架执行 sql并将结果映射为 java对象并返回。(从执行 sql到返回 result的过程)。
(2)Mybatis的优缺点?
优点:
1)基于 SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在 XML里,解除 sql与程序代码的耦合,便于统一管理;提供 XML标签,支持编写动态 SQL语句,并可重用。
2)与 JDBC相比,减少了 50%以上的代码量,消除了 JDBC大量冗余的代码,不需要手动开关连接;
3)很好的与各种数据库兼容(因为 MyBatis使用 JDBC来连接数据库,所以只要 JDBC支持的数据库,MyBatis都支持)。
4)能够与 Spring很好的集成;
5)提供映射标签,支持对象与数据库的 ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
缺点:
1)SQL语句的编写工作量大,如果涉及到字段多,或者需要进行多表联合查询,那么需要写很多的sql语句;
2)SQL语句依赖于数据库,导致数据库移植性差,无法更换数据库。
(3)Mybatis的工作原理以及实现代码?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6namyeUI-1672325209454)(F:\\姚飞鑫\\image-20210831112631631.png)]
1、加载mybatis全局配置文件(数据源、mapper映射文件等),解析配置文件,MyBatis基于XML配置文件生成Configuration,和一个MappedStatement(包括了参数映射配置、动态SQL语句、结果映射配置),其对应着<select | update | delete | insert>标签项。
2、SqlSessionFactoryBuilder通过Configuration对象生成SqlSessionFactory,用来开启SqlSession。
3、SqlSession对象完成和数据库的交互:
(1)用户程序调用mybatis接口层api(即Mapper接口中的方法)
(2)SqlSession通过调用api的Statement ID找到对应的MappedStatement对象
(3)通过Executor(负责动态SQL的生成和查询缓存的维护)将MappedStatement对象进行解析,sql参数转化、动态sql拼接,生成jdbc Statement对象
(4)JDBC执行sql。
(5)借助MappedStatement中的结果映射关系,将返回结果转化成HashMap、JavaBean等存储结构并返回。
实现代码:
public void test03() throws IOException
String resource = "mybatis.xml";
//以输入流的方式加载配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过配置文件中参数构建mybatis环境 --》 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//SqlSession 操作数据库
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.update("PersonMapper.updatePerson",new Person(4,"yao",new Date(),"guangzhou"));
//如果是使用上面的语句的话,那么则需要去记住Mapper里面需要的语句的id,这样子需要记忆性。那么则有另一种方案可以不需要进行记忆
PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);
Person person=personMapper.findPersonByIdOrName(6,"刘");
//结论:使用接口代理开发好处是,最终打包时可以把配置文件和接口放在一起,并且开发时不需要记忆方法名,直接通过接口对象调用即可。大大降低程序员开发的难度。在使用的时候,需要在mybatis的配置文件中,配置扫描mapper接口类的包
<mappers>
<package name="com.yfx.day01.mapper"></package>
</mappers>
sqlSession.commit();
sqlSession.close();
上面在进行mybatis的准备操作时,每一个测试方法都需要进行写相同的代码,那么这样会增加代码的冗余性。可以把每次进行准备操作的代码抽取出来,作为一个工具类,在测试类中使用继承方式,就可以使每一个测试的方法不用写很多的相同的代码了。
public class BaseUtils
private static SqlSessionFactory sqlSessionFactory;
public SqlSession sqlSession =null;
static
try
String resource = "mybatis.xml";
//以输入流的方式加载配置文件
InputStream inputStream = null;
inputStream = Resources.getResourceAsStream(resource);
//通过配置文件中参数构建mybatis环境 --》 SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
catch (IOException e)
e.printStackTrace();
public static SqlSession sqlSession()
return sqlSessionFactory.openSession();
//这是sqlSession执行之前
@Before
public void before()
sqlSession = BaseUtils.sqlSession();
//这是sqlSession执行之后
@After
public void after()
sqlSession.commit();
sqlSession.close();
/*
* 这样子在测试类,就大大减少了准备操作的代码了
* 比如下面
**/
public class Persontest extends BaseUtils
@Test
public void Test01()
PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class); //需要加载mapper
Person person = personMapper.selectByPrimaryKey(4);
System.out.println(person);
(4)#和$的区别是什么?
#是预编译处理,$是字符串替换,EL表达式。
Mybatis 在处理#时,会将 sql 中的#替换为?号,调用 PreparedStatement 的set 方法来赋值;
Mybatis 在处理$时,就是把$替换成变量的值。
使用#可以有效的防止 SQL 注入,提高系统安全性
(5)Mybatis的动态sql有哪些?
Mybatis动态 SQL可以在 Xml映射文件内,以标签的形式编写动态 sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接 sql的功能。
<!-- where if 的用法-->
<select id="findPersonByCondition" resultType="person">
select * from person
<!--在sql中写查询判断 使用where 标签,可以省略多余的and,并且当一个条件都不存在时, 去除where关键字 if做条件判断语句 -->
<where>
<if test="id>0">
and id=#id
</if>
<if test="name!=null and name != ''">
and name=#name
</if>
</where>
</select>
<!-- where choose when otherwise -->
<select id="findPersonByCondition" resultType="person">
select * from person
<where>
<choose>
<when test="id>0">
and id= #id
</when>
<when test="name!=null and name != ''">
and name = #name
</when>
<otherwise>
and 1=1
</otherwise>
</choose
</where>
</select>
<!--
总结:
1)使用if的标签的,可以同时满足多个条件;
2)使用when ,只能满足一种条件,如果满足的话,会去除and,如果都不满足的话,会使用otherwise;
3)choose 类似 switch选择结构,otherwise 类似 switch中的 default,类似 if中的else
-->
<!--set
标签<set> 相当于 关键字set.
set 除了作为关键字,还可以去除多余的, -->
<update id="UpdatePersonByCondition">
update person
<set>
<if test="name!=null and name != ''">
name=#name,
</if>
<if test="birthday!=null">
birthday=#birthday,
</if>
</set>
where id = #id
</update>
<!--trim-->
<!--
if 判断,可以同时满足多个条件
choose 选择结构
when 判断,只能满足其中一个。可以写otherwise ,相当于else,switch 里面的 default
set 替换set关键字,去掉最后一个 ,
where 替换where关键字,去掉第一个and 或者 or
trim标签的用法相对比 set、where更加全能,既可以实现动态查询,也可以实现动态修改、添加。
-->
<!-- trim prefix 前缀 suffix 后缀 prefixOverrides 过滤第一个指定的关键字 suffixOverride 过滤最后一个指定的关键字 -->
<update id="UpdatePersonByCondition">
update person
<trim prefix="set" suffix="where id=#id" suffixOverrides=",">
<if test="name!=null and name != ''">
name=#name,
</if>
<if test="birthday!=null">
birthday=#birthday,
</if>
<if test="addr!=null and addr != ''">
addr=#addr,
</if>
</trim>
</update>
<!--foreach 可以用来批量查询或者批量删除-->
<!--
foreach 遍历集合的标签
collection 指定传入参数的类型 collection list array
open 循环开始前的字符
close 循环结束后的字符
item 集合,数组中循环的元素,必须要和下面的#里面的一样
separator 循环中元素之间的连接符
index 下标(不常用)
-->
<select id="findPersonByIds" resultType="person">
select * from person
<foreach collection="list" open="where id in(" close=")" item="i" separator=",">
#i
</foreach>
</select>
<delete id="DeletPersonByIds">
delete from person
<foreach collection="list" open="where id in(" close=")" item="i" separator=",">
#i
</foreach>
</delete>
(6)Mybatis的XML映射文件的标签有哪些,分别用于哪些操作?
<select><update><insert><delete><resultType><resultMap><ossociation><collection><selectKey><sql><include>
前面四个就是crud语句了。resultType 采用的是自动映射,也就是映射和列名相同的结果到属性中;
当列名和属性名不一致,自动映射失败,需要使用手动映射(自定义映射),这时候就需要使用了resultMap
resultMap id 表示给当前resultMap 取一个名字,唯一标志 type 表示需要给哪个javabean 自定义映射规则 autoMapping 表示其他一样的名字的话,采用自动映射
那它里面的字标签有 column :表中的字段名 列名 ; property :对象的属性名; 在里面写具体哪一个是列名和属性名不一致的。
使用resultMap的标签有两种:第一种是上面这种情况;第二种是使用多表查询:一对一()、一对多()。下面的第七点会说到。
主键生成策略:
适用场景:在实际工作中,经常会有以下情况,需要马上显示、获取刚刚插入的数据的主键。比如添加订单、订单项
<insert id="InsertPersonJavaBean" >
<!--
order 设置 selectKey 中语句什么时候执行 before after
ketcolumn 把记录中哪个字段值拿过来
keyproperty 拿回来的字段值映射给哪个属性
resultType 字段值的类型
-->
<selectKey order="AFTER" keyColumn="id" keyProperty="id" resultType="int">
select LAST_INSERT_ID()
</selectKey>
insert into person(name,birthday,addr) <include refid="insertsql"></include> ;
</insert>
sql片段: 使用来包含导入。
适用场景:使用sql片段可以把某几段sql中 重复的代码抽取出来,当需要使用时,使用标签 引用即可,可以提高代码的可重用性,更加方便维护
可以将xml映射文件中,出现多条语句中,共同的sql部分。为了不让整个配置文件中本来出现相同的sql片段出现多次,可以使用sql标签,把共同部分抽取出来,放在标签里面,同时给sql标签赋予一个id。使用标签 中的属性refid=“ id ”可以将对应id的sql片段导进去。
补充一个重要的注解:在mybatis调用方法时,有可能传入的参数不只一个,可能有多个。这时候我们可以采用注解的方式 @Param(“ ”)
使用**@Param** 可以给指定参数赋值一个参数名,这个参数名是可以在xml中通过#获取。
@Param: 1.多个参数会使用
2.模糊查询如果使用 $,默认是以value作为参数名,也就是必须使用 $value ,也需要使用@Param
3.使用动态sql,并且传入的参数是基本类型,也会使用@Param
(7)Mybatis的一对一、一对多关联查询和嵌套查询?
只要发生连表查询,resultType 就不能处理得了结果映射的问题。需要使用resultMap 处理多张表之间字段和属性之间自定义映射。
处理一对一的关联映射:使用
property 表示关联映射哪个属性 javaType 表示关联属性对应的类型(所在的实体类) autoMapping 其余自动映射。
处理一对多的关联映射:使用
property 表示关联映射哪个属性 ofType 指的是集合中泛型的类型(所在的实体类) autoMapping 其余自动映射。
嵌套查询是延迟加载的前提。之前的关联查询,是在一次查询中查询多个表。
嵌套查询就是把多个表拆成一个一个表,每次执行单表,执行多次查询。
因此嵌套查询就只是在关联查询的基础上,在和中,加入select 属性:表示需要嵌套的另一个查询语句,通过namespace.id 确定下来 和 column 属性 表示 另一个嵌套查询语句的条件。
(8)Mybatis是否支持延迟加载?
延迟加载在 mybatis 中有两种实现方式: 局部延迟加载、全局延迟加载。
延迟加载必须要建立在使用 嵌套查询(因此仅支持一对一和一对多,多对多)的情况下才可以使用
Mybatis仅支持 association关联对象和 collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在 Mybatis配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
实现局部延迟加载策略非常简单,只需要在collection、association标签中 加上属性 <fetchType = “lazy”>。 默认是eager(立即加载的意思)
实现全局加载,在mybatis.xml中配置开启全局延迟加载。
<!--这是全局加载的,默认是false-->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
需要注意的是,如果全局延迟加载开启,局部加载策略也开启,会优先选择局部的策略方式。
延迟加载的执行原理:
它的原理是,使用 CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现a.getB()是 null值,那么就会单独发送事先保存好的查询关联 B对象的 sql,把 B查询上来,然后调用 a.setB(b),于是 a的对象 b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
(9)Mybatis的一级缓存、二级缓存?
文档里面的:
一级缓存简介:一级缓存是SqlSession级别的缓存,是默认开启且无法关闭的。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象多次调用同一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
一级缓存要求多次查询的语句需要使用同一个sqlSession,并且执行sql和参数需要一样。
二级缓存简介:因为一级缓存存储有效范围是SqlSession,范围比较小,在实际开发中,用处并不大。实际开发中难免会经常查询数据库,为了提高数据库查询效率。使用二级缓存是一个不错的选择。
二级缓存有效范围是Mapper级别。只要两次、多次查询的方法来自于同一个namespace,name就可以使用二级缓存。
注意:
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession级别的。
使用二级缓存的实现步骤:
第一步:在需要实现二级缓存的JavaBean类中实现序列化接口 implements Serializable;
第二步:在需要实现二级缓存的xml映射文件中添加一个标签 <cache/>。
1)一级缓存:基于 PerpetualCache的 HashMap本地缓存,其存储作用域为 Session,当 Sessionflush或 close之后,该 Session中的所有 Cache就将清空,默认打开一级缓存且不能关闭。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要手动开启二级缓存,使用二级缓存属性类需要实现 Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置。
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了 C/U/D操作后,默认该作用域下所有 select中的缓存将被 clear。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oitP6v20-1672325209459)(C:\\Users\\Administrator\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210816204114405.png)]
(10)关于Mybatis的分页?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
(一)原生的Mybatis分页
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oulS3C90-1672325209460)(C:\\Users\\Administrator\\AppData\\Roaming\\Typora\\typora-user-images\\image-20211002195616293.png)]
所以底层使用了RowBounds这个对象的两个属性(start,limit)来进行截取分页的。
缺点:内存分页(即逻辑分页),是通过将数据全部查询出来放在内存中进行的。
(二)直接写sql语句(使用limit)
(三)使用PageHelper:第三方插件 【物理分页】
步骤:
(1)导入依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
(2)在Mybatis配置文件中配置:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
(3)使用。可以在测试类进行测试一下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Zg1HTgz-1672325209461)(C:\\Users\\Administrator\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210816204005495.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O7TopWD8-1672325209462)(C:\\Users\\Administrator\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210816204029040.png)]
(11)传统的JDBC
步骤:
1、加载驱动 所以直接使用反射的机制 class.forName 会自动去加载里面的静态代码块 注册驱动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W7p4XH5f-1672325209463)(C:\\Users\\Administrator\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210817171339526.png)]
2、连接数据库 DriverManager
3、获得执行sql的对象 Statement PreparedStatement
4、如果是查询的话,需要获得返回的结果集 ResultSet
5、释放连接
使用不安全的执行sql对象: Statement
public class JdbcTest
public static void main(String[] args)
try
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/day01?useSSL=true&useUnicode=true&characterEncoding=utf8";
String username = "root";
String password = "";
//2.连接成功 数据库对象 Connection 代表数据库 URl和用户信息
Connection connection = DriverManager.getConnection(url, username, password);
//3.执行数据库对象 Statement
Statement st = connection.createStatement(); // 缺点:会出现sql注入的危险 比如 select * from person where name = '' or '1=1';这样子数据库中的所有数据都泄露出来了
//执行sql的对象 去执行SQL,可能存在结果,查询返回的结果
String sql = "select * from person where id =4";
//4.返回的结果集 ,结果集中封装了我们全部的查询结果
ResultSet rs = st.executeQuery(sql);
while (rs.next())
System.out.println(rs.getInt("id"));
System.out.println(rs.getString("name"));
System.out.println(rs.getDate("birthday"));
System.out.println("================================");
//5.释放连接
rs.close以上是关于Java面试题-SSM框架的主要内容,如果未能解决你的问题,请参考以下文章