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框架的主要内容,如果未能解决你的问题,请参考以下文章

Java面试题-SSM框架

Java面试题-SSM框架

Java面试题-SSM框架

Java-SSM框架相关面试题整理!面试真题解析

SSM(Spring + Springmvc + Mybatis)框架面试题

SSM框架面试题整理