[SSM架构]springboot笔记
框架基础介绍
框架概念
框架(Framework)是一个半成品软件,将所有的、公共的、重复的功能解决掉,帮助程序快速高效的进行开发,他是可重复的,可拓展的。
常见的框架--SSM
Spring:他是整合其他框架的框架,他的核心是IOC和AOP,他由20多个模块构成,在很多领域都提供了很好的解决方案。
SpringMVC:专门用来优化控制器(servlet),提供了极容易的数据提交,数据携带,页面跳转等功能。
Mybatis:是持久化层的一个框架,用来进行数据库访问的优化,专注于sql语句,极大的简化了JDBC的访问。
项目搭建
新建项目Spring Initializr
选择启动服务url除了默认通用url之外,可选择阿里云https://start.aliyun.com/
Spring Initializr项目配置
组和工件
组(GroupID)是项目组织的唯一标识符,实际对应JAVA的结构,是main目录里java的目录结构。
工件(ArtifactID)是项目的唯一标识符,实际对应项目的名称,就是项目根目录的名称。
命名规则:
1.GroupID一般分为多个段,第一个段为域,第二个段为公司名。域又分为org、com、cn等,其中org为非盈利组织,com为商业组织。
2.ArtifactID是项目名称。
Jar和War包
Jar、War在文件结构上,二者并没有什么不同,他们都采用zip或者rar档案文件压缩格式。但是他们的使用目的是有区别的:
Jar:
1.Jar文件(拓展名为.Jar,Java Application Archive)包含Java类的普通库、资源(resource)、辅助文件(auxiliary files)等。
2.Jar包是Java打的包,一般只是包括一些编译后的class文件和一些部署文件,在声明了Main_class之后是可以用java命令运行的,例如java -jar xxxx.jar。
3.Jar包通常是开发时要引用通用类,打成包成便于存放管理。
War:
1.War文件(拓展名为.War,Web Application Archive)包含全部Web应用程序。在这种情形下,一个Web应用程序被定义为单独的一组文件、类和资源,用户可以对War文件进行封装,并把他作为小型服务程序(servlet)来访问。
2.War包可以理解为JavaWeb打的包,是一个web模块,包括写的代码编译成的class文件,依赖的包,配置文件,所有的网站页面,包括html,jsp等等。一个war包可以理解为是一个web项目,里面是项目的所有东西。
3.War包需要发布到一个容器里面,那Tomcat来说,将War文件包放置在他的\webapps\目录下面,启动Tomcat,这个包可以自动进行解压,也就是你的web目录,相当于发布了。
4.WAR是Sun提出的一种Web应用程序格式,与JAR类似,也是许多文件的一个压缩包。这个包中的文件会按照一定的目录结构来组织:通常其根目录下包含有Html和JSP文件或者包含这两种文件的目录,另外还有一个WEB-INF目录,这个目录很重要。通常在WEB-INF目录下有一个web.xml文件和一个classes目录,web.xml是这个应用的配置文件,而classes目录下则包含编译好的Servlet类和Jsp或者Servlet所依赖的其他类(JavaBean)。
关于Java版本
推荐学习Java8,因为Java8现在是最稳定的版本,并且支持现有的99%的框架,所以大家一直在使用Java8,对于一个成熟的公司来说,代码只是辅助业务的手段,而非目的。
从Java9开始,Java版本的发布开始让人有些眼花缭乱了,每隔6个月,都会冒出一个新版本出来,Java10,Java11,Java12,Java13,到2020年8月份,Java15正式发布。
这么多的版本中,只有Java8,Java11和未来的Java17是长期支持版本(LTS).
这里指定Java版本为8
选择依赖项
在新建项目中添加依赖项:
必定添加开发工具(Spring Boot DevTools)和Web(Spring Web)
其他依赖项可以在此处(新建项目)中添加,也可以在后续生成的pom.xml文件中使用Maven添加
在pom.xml中添加依赖项目方法:
1.在pom.xml文件中找到<dependencies>标签,该标签包裹的就是当前所有依赖相对应的jar包声明
2.在<dependencies>标签中添加想要的依赖,
方法一:可直接输入依赖项的名称,自动弹出提示,回车确定添加,例如:添加单元测试Junit所需依赖,输入junit自动弹出提示,回车确定添加
方法二:可访问https://mvnrepository.com/,在搜索栏中直接搜索
例如:添加mybatis所需依赖
选择第一个搜索结果,选择对应的版本,(以3.4.6版本为例)
复制内容,粘贴到pom.xml文件中的<dependencies>标签内,点击idea应用界面工作区的右上角的Maven按钮,自动添加依赖
注意:本项目中需要搭建Mybatis开发环境,无论是使用在新建项目中添加依赖的方法,还是在pom.xml中手动添加依赖的方法,都必须添加mybatis和mysql这两个依赖项
项目名称和位置
自定义项目名称和位置
点击确定,项目构建成功
一些问题的解决办法
pom.xml文件version报错
解决办法一:版本号过高,降低版本号(一般会自动匹配jdk生成对应版版本号)
解决办法二:在IDEA中的Maven中配置错误
点击文件->设置
在构建、执行、部署工具->Maven中更改maven主路径
使用Mybatis框架结构搭建项目
MyBatis概述
1.MyBatis是一个优秀的基于Java的持久层框架,内部封装了JDBC,开发者只需要关注sql语句本身,而不需要处理加载驱动、创建链接、创建statement、关闭链接,资源等繁杂过程。
2.MyBatis通过xml或注解两种方式将要执行的各种sql语句配置起来,并通过Java对象和sql的动态参数进行映射生成最终执行的sql语句,最后由mubatis框架执行sql并将结果映射为Java对象并返回。
3.MyBatis解决的主要问题:
1)减轻使用JDBC的复杂性,不用编写、重复创建Connection ,Statement;
2)不用编写关闭资源代码;
3)直接使用Java对象,表示结果数据;
MyBatis框架结构
1、 mybatis配置文件
【SqlMapConfig.xml】:此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
【mapper.xml】:文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。
【SqlMapConfig.xml】:是mybatis的核心文件。mybatis将dao层与sql语句分离开来,虽然写的时候分离开来了,但是执行的时候还是要依靠sql语句,所以我们的sql语句写在Mapper.xml中。我们在加载核心的时候,会加载他下面的Mapper.xml,所以sql语句便会加载进去了。我们只需要在SqlMapConfig.xml中引入Mapper.xml就可以了,所以最后只需要加载SqlMapConfig.xml这一个核心配置文件。
2、 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂。工厂能帮我们去加载核心配置文件。加载了核心配置文件后就创建session,通过session可以对数据库进行操作。
3、 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。
4、 mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。Executor是执行者,我们不需要管,因为mybatis已经为我们封装好了。mybatis直接执行sql语句。
5、 Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。
6、 Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
7、 Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
8、Mapped Statement是输入与输出中间过程中产生的一些对象,通过这些对象去访问数据库。
核心配置文件
添加mybatis-config.xml核心配置文件
在src/main/resource下新建添加mybatis-config.xml
在mybatis-config.xml写入模板代码
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
添加db资源包
在src/main/resource下新建->资源包,指定包名为db
在新建的db资源包内写入mysql数据库链接信息
下面的代码有五行(包括注释)每行后面不能有空格,每个字母之间也不能有空格
1 2 3 4 5 | #mysql数据库链接信息<br>#com.mysql.jdbc.Driver高版本数据库链接与添加cj:com.mysql.cj.jdbc.Driver driver=com.mysql.jdbc.Driver url=jdbc:mysql: //localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC username=root password=root |
在mybatis-config.xml文件<configuration>标签中添加链接数据库相关代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <!-- 读取外部的数据源配置信息--> <properties resource= "db.properties" ></properties> <!-- 指定类别名所在的包路径--> <typeAliases> < package name= "com.example.demo" /> </typeAliases> <environments default = "demo" > <environment id= "demo" > <!-- 使用JDBC的事务管理器--> <transactionManager type= "JDBC" ></transactionManager> <!-- 以连接池方式连接mydql数据库--> <dataSource type= "POOLED" > <property name= "driver" value= "${driver}" /> <property name= "url" value= "${url}" /> <property name= "username" value= "${username}" /> <property name= "password" value= "${password}" /> </dataSource> |
添加SQL映射文件
在src/main/resources下新建一个mapper目录,在该目录下新建GradeMapper.xml文件
在GradeMapper.xml中写入模板代码
1 2 3 4 | <?xml version= "1.0" encoding= "UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > |
添加<mapper>标签,并继续往<mapper>标签内添加数据库操作相关语句,例如
1 2 3 4 5 | <!--com.example.demo.Dept.queryCount--> <mapper namespace= "com.example.demo.Dept" ><!-- 名字空间自己随便,一般指定所用的类的位置 --> <select id= "queryCount" resultType= "Long" > select count( 1 ) from tb_dept </select><br></mapper> |
在mybatis-config.xml的dependencies标签下添加mapper文件的映射
1 2 3 4 | <!-- mapper映射文件 --> <mappers> <mapper resource= "mapper/GradeMapper.xml" ></mapper> </mappers> |
添加实体类
新建一个java类用作数据库实体映射的实体类,添加Serializable接口,然后添加属性和get/set以及toString方法,例如:
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 | public class Dept implements Serializable { private Integer deptNo; private String deptName; private String remark; public Integer getDeptNo() { return deptNo; } public void setDeptNo(Integer deptNo) { this .deptNo = deptNo; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this .deptName = deptName; } public String getRemark() { return remark; } public void setRemark(String remark) { this .remark = remark; } @Override public String toString() { return "Dept{" + "deptNo=" + deptNo + ", deptName='" + deptName + '\ '' + ", remark='" + remark + '\ '' + '}' ; } } |
关于Serializable接口:
Serializable是一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现Serializable接口。而实际上,Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。
序列化的应用场景
1. 比如说你的内存不够用了,那计算机就要将内存里面的一部分对象暂时的保存到硬盘中,等到要用的时候再读入到内存中,硬盘的那部分存储空间就是所谓的虚拟内存。在比如过你要将某个特定的对象保存到文件中,我隔几天在把它拿出来用,那么这时候就要实现Serializable接口。
2. 在进行Java的Socket编程的时候,你有时候可能要传输某一类的对象,那么也就要实现Serializable接口。最常见的你传输一个字符串,它是JDK里面的类,也实现了Serializable接口,这样做为的是将数据变为二进制来传输,所以可以在网络上传输。
3. 如果要通过远程的方法调用(RMI)去调用一个远程对象的方法,如在计算机A中调用另一台计算机B的对象的方法,那么你需要通过JNDI服务获取计算机B目标对象的引用,将对象从B传送到A,就需要实现序列化接口。
备注:
Serializable这个接口其实是个空接口。其实,看一下接口的注释说明就知道,当我们让实体类实现Serializable接口时,其实是在告诉JVM此类可被序列化,可被默认的序列化机制序列化,不需要我们实现。
关于@Data注解
1、@Data注解是lombok.jar包下的注解,该注解通常用在实体bean上,不需要写出set和get方法,但是具备实体bean所具备的方法,简化编程提高变成速度。
2、@Data相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集。
新建一个MybatisTest类用来测试
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 | public class MybatisTest { public static void main(String[] args) { InputStream inputStream = null ; SqlSessionFactoryBuilder builder = null ; SqlSessionFactory sessionFactory = null ; SqlSession session = null ; try { // 读取mybatis-config.xml文件 inputStream = Resources.getResourceAsStream( "mybatis-config.xml" ); // 创建工厂构建器 builder = new SqlSessionFactoryBuilder(); // 创建会话工厂 sessionFactory = builder.build(inputStream); // 创建会话 session = sessionFactory.openSession(); // 通过映射文件中的namespace.id的形式找到相对应的方法 //此处添加查询、插入、更改、删除等数据库操作代码 } catch (Exception e) { session.rollback(); //插入、删除等commit()修改数据库的操作要写回滚函数 e.printStackTrace(); } finally { // 关闭会话 session.close(); } } } |
注意:此时导入的方法必须都是与mybatis有关的
1 2 3 4 | import org.apache.ibatis.io.Resources; import org.apache.ibatis.io.session.SqlSession; import org.apache.ibatis.io.session.SqlSessionFactory; import org.apache.ibatis.io.session.SqlSessionFactoryBuilder; |
单元测试方法
添加依赖
在pom.xml的<dependencies>标签中添加Junit相关依赖
1 2 3 4 5 6 | <!-- junit单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version> 3.8 . 1 </version> </dependency> |
添加测试类
在src/test目录下添加要测试的类的同名+Test新建类,如:关于Class的测试ClassTest
并在要测试的方法前使用@Test
在测试类中,添加要构造函数,例如:Example example=new Example();
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class ExampleTest { Example example= new Example(); @Test public void add(){ int s=example.add( 10 , 20 ); System.out.println( "s=" +s); } @Test public void sum(){ int sum =example.sum(); System.out.println( "sum=" +sum); } } |
数据库的简单操作
不带参数的单表查询
在MybatisTest类Main中,返回一条数据用selectOne()查询,Long型变量接收查询结果,例如:
1 2 | // 通过映射文件中的namespace.id的形式找到相对应的方法 Long count = session.selectOne( "abc.queryCount" ); |
返回多条数据用selectList()查询,用List<Object>型变量接收查询结果,例如:
1 2 3 4 | List<Emp> list = session.selectList( "emp.dao.queryAllEmp" ); for (Emp emp : list) { System.out.println(emp.toString()); } |
在select()系列函数中第一个参数是所在的页面通过映射文件中的namespace.id的形式,例如:
emp.dao.queryAllEmp,对应src/main/resources/mapper/EmpMapper.xml里的namespace和id,在<select>标签内写数据库查询语句:
1 2 3 4 5 6 | <mapper namespace= "emp.dao" > <!-- 查询表中总记录数 --> <select id= "queryAllEmp" resultType= "Emp" > select * from tb_emp </select> </mapper> |
其中的返回的类型resultType则对应MybatisTest类main()中的接收参数的List<Object>类型的Object内的内容,例如List<Emp>
带参数的单表查询
selectOne()和selectList有两个参数,
第二个参数:可以传递的参数。
要传递单个参数直接填入,多个参数则需要将其写成一个对象或者使用hashMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //多参数传递 Dept dept1 = new Dept(); dept1.setDeptNo( 1003 ); dept1.setDeptName( "测试部" ); List<Dept> list1 = session.selectList( "abc.queryDept" , dept1); // map中key必须和映射文件中的占位符名称完全一样 HashMap<String, Object> map = new HashMap<String, Object>(); map.put( "deptName" , "测试部" ); map.put( "deptNo" , 1003 ); List<Dept> list2 = session.selectList( "abc.queryDept" , map); for (Dept dept : list1) { System.out.println(dept.toString()); } |
在对应的映射文件src/main/resources/mapper/GradeMapper.xml中<select>标签内添加元素:parameterType,它的值对应select系列函数的第二参数的类型
1 2 3 4 5 6 | //<select id="queryDept" resultType="Dept" parameterType="Dept"> <select id= "queryDept" resultType= "Dept" parameterType= "Map" > select * from tb_dept d where d.deptNo=#{deptNo} and d.deptName=#{deptName} </select> |
多参数的插入
在MybatisTest类的Main中,用insert()函数写数据库插入代码,多参数传递使用Object或则HashMap类型(案例参照带参数的单表查询):
1 2 3 4 5 6 | Dept dept = new Dept(); dept.setDeptNo( 1004 ); dept.setName( "研发部" ) session.insert( "com.example.demo.Dept.addDept" ,dept);<br> //插入、删除、修改等对数据库有影响的操作需要写commit(),来提交事务 session.commit(); System.out.println( "操作成功" ); |
在对应的映射文件src/main/resources/mapper/GradeMapper.xml中<insert>标签内添加元素:parameterType,它的值对应insert函数的第二参数的类型
1 2 3 4 | <insert id= "addDept" parameterType= "Dept" > insert into tb_dept value (#{deptNo},#{deptName},#{remark}) </insert> |
修改和删除
在MybatisTest类的Main中,用update()函数写数据库修改和删除代码:
1 2 3 4 5 6 7 8 9 | // 修改 Dept dept = new Dept(); dept.setDeptNo( 1004 ); dept.setName( "研发部" ) session.update( "com.example.demo.Dept.updateDept" ,dept); System.out.println( "操作成功" ); // 删除 session.update( "com.example.demo.Dept.delDept" , 1004 );<br> //插入、删除、修改等对数据库尊影响的操作需要写commit()来提交事务<br> session.commit();<br> System.out.println("操作成功"); |
在对应的映射文件src/main/resources/mapper/GradeMapper.xml中<update>或<delete>标签内添加元素:parameterType,它的值对应update函数的第二参数的类型
1 2 3 4 5 | <update id= "updateDept" parameterType= "Dept" > update tb_dept d set d.deptName=#{deptName},remark=#{remark} where d.deptNo=#{deptNo} </update> |
1 2 3 4 | <delete id= "delDept" parameterType= "int" > delete from tb_dept where deptNo=#{deptNo} </delete> |
多表查询(一对多)
涉及到多个表的查询要对实体类进行修改,例如以tb_dept部门表作为主表,tb_emp员工表的deptNo作为外键,需要在Dept类中添加集合属性:
1 2 3 4 5 6 | public class Dept implements Serializable { private Integer deptNo; private String deptName; private String deptRemark; private List<Emp> emps = new ArrayList<Emp>(); // 集合属性:多个员工 } |
然后更新get/set方法和toString方法;
在Emp类中添加映射对象属性:
1 2 3 4 5 6 7 | public class Emp implements Serializable { private Integer empNo; private String empName; private Double salary; private Date hireDate; private Dept dept; // 映射对象属性 } |
并更新get/set 方法,但是toString()方法不需要重写。
在MybatisTest类的Main中写入查询相关代码
1 2 3 4 5 | // 查询特定员工所在的部门 List<Emp> list2 = session.selectList( "emp.dao.queryDeptByEmp" ); for (Emp emp : list2) { System.out.println(emp.getEmpName() + "\t" + emp.getDept().getDeptName());} session.commit(); |
1 2 3 4 5 6 7 8 9 10 11 12 | // 根据部门查询员工 List<Dept> list1 = session.selectList( "abc.queryEmpByDept" ); for (Dept dept : list1) { System.out.println(dept.toString()); List<Emp> emps = dept.getEmps(); for (Emp emp : emps) { System.out.println(emp.toString()); } } session.commit(); System.out.println( "==========操作成功" ); |
在对应的映射文件src/main/resources/mapper/GradeMapper.xml中写入二次映射实体对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <!-- 二次映射实体对象Dept --> <resultMap id= "deptMap" type= "Dept" > <id property= "deptNo" column= "deptNo" ></id> <result property= "deptName" column= "deptName" ></result> <result property= "deptRemark" column= "remark" ></result> <!-- 集合属性的映射--> <collection property= "emps" ofType= "Emp" > <id property= "empNo" column= "empNo" ></id> <result property= "empName" column= "empName" ></result> <result property= "salary" column= "salary" ></result> <result property= "hireDate" column= "hireDate" ></result> </collection> </resultMap> |
接着写入select查询语句结果返回值为二次映射对象id
1 2 3 4 5 6 7 8 9 10 | <!-- 查询特定员工所在的部门--> <select id= "queryDeptByEmp" resultMap= "empMap" > SELECT e.empName,d.deptName FROM tb_dept d,tb_emp e WHERE d.deptNo = e.deptNo AND e.empName = '李四' </select> |
在另一表对应的映射文件src/main/resources/mapper/EmpMapper.xml也写入二次映射实体对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <resultMap id= "empMap" type= "Emp" > <id property= "empNo" column= "empNo" ></id> <result property= "empName" column= "empName" ></result> <result property= "salary" column= "salary" ></result> <result property= "hireDate" column= "hireDate" ></result> <!-- 映射对象属性 --> <association property= "dept" column= "deptNo" javaType= "Dept" > <id property= "deptNo" column= "deptNo" ></id> <result property= "deptName" column= "deptName" ></result> <result property= "deptRemark" column= "remark" ></result> </association> </resultMap> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <select id= "queryEmpByDept" resultMap= "deptMap" > SELECT e.empName, e.hireDate, d.deptName FROM tb_dept d, tb_emp e WHERE d.deptNo = e.deptNo AND d.deptName = '人事部' </select> <select id= "queryDept" resultMap= "deptMap" > select * from tb_dept d </select> |
接着写入select查询语句结果返回值为二次映射对象id
至此需要实现的内容代码已经基本书写完成,接下来是代码优化,通过观察对比两个表的二次映射对象不难发现,其中有两端的代码重复,如tb_dept表的映射对象属性在tb_emp表的二次映射对象中重复tb_emp表也有相似的代码重复。
由于这两个映射文件都在src/main/resources/mybatis-config.xml中加在了
所以,这两映射文件可以添加resultMap={“”}属性相互引入,即
动态条件查询
<if>和<where>示例:
<if>和<set>示例:
<trim>和<if>示例:
子查询
子查询示例:
在src/main/java/com/example/mybatisdemo/test/MybatisTest.java类的Main中
1 2 3 4 5 | Integer[] ids={ 1001 , 1002 }; List<Dept> list =session.selectList( "abc.subQueryDept" ,ids); for (Dept d :list){ System.out.println(d.toString()); } |
在对应的映射文件src/main/resources/mapper/GradeMapper.xml中:
1 2 3 4 5 6 7 8 | <select id= "subQueryDept" resultMap= "deptMap" > //select * from tb_dept d where d.deptNo in(1001,1002) select * from tb_dept d where d.deptNo in <foreach collection= "array" item= "ids" open= "(" close= ")" separator= "," > #{ids} </foreach> </select> |
其中除了用数组的形式,还可以用hashMap来写MybatisTest.java类中的查询代码:
1 2 3 4 5 6 7 8 9 10 11 | //子查询 ArrayList<Integer> arys = new ArrayList<>(); arys.add( 1001 ); arys.add( 1002 ); HashMap<String, List> ids = new HashMap<>(); ids.put( "array" ,arys); // Integer[] ids={1001,1002}; List<Dept> list =session.selectList( "abc.subQueryDept" ,ids); for (Dept d :list){ System.out.println(d.toString()); } |
分页查询
分页查询示例:
在src/main/java/com/example/mybatisdemo/test/MybatisTest.java类的Main中
1 2 3 4 5 6 7 8 | // 分页查询 HashMap<String, Object> map = new HashMap<>(); map.put( "index" , 0 ); map.put( "pageSize" , 2 ); List<Dept> list = session.selectList( "abc.queryDeptByPage" ,map); for (Dept d : list){ System.out.println(d.toString()); } |
在对应的映射文件src/main/resources/mapper/GradeMapper.xml中:
1 2 3 4 | <select id= "queryDeptByPage" resultMap= "deptMap" parameterType= "map" > select * from tb_dept limit #{index},#{pageSize} </select> |
模糊查询
方法1(通用):在src/main/java/com/example/mybatisdemo/test/MybatisTest类Main中
1 2 3 4 5 6 7 8 | //模糊查询 Dept dept2= new Dept(); dept2.setDeptName( "%" + "财" + "%" ); List<Dept> fuzzyDeptNameList = session.selectList( "deptSpace.fuzzyQueryDeptName" , dept2); System.out.println( "模糊查询" ); for (Dept d : fuzzyDeptNameList){ System.out.println(d.toString()); } |
在对应的映射文件src/main/resources/mapper/GradeMapper.xml中:
1 2 3 4 | <!-- 方法 2 :仅限mysql使用concat--> <!--select * from tb_dept where deptName like CONCAT( '%' , '人' , '%' );--> <select id= "fuzzyQueryDeptName" resultMap= "deptMap" > select * from tb_dept where deptName like #{deptName}</select> |
使用三层架构搭建项目
三层架构概述
1、三层架构包含的三层:
界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。
2、三层的职责:
界面层:接受用户的数据,调用业务逻辑层进行功能处理,返回结果给客户端,过去的 servlet 就是界面层的功能。
业务逻辑层:用来进行整个项目的业务逻辑处理,向上为界面层提供处理结果,向下问数据访问层要数据。
数据访问层:专门用来进行对数据的增、删、改、查操作,向上为业务逻辑层提供数据。
3、三层之间处理请求的交互:
(客户端)<--->界面层<--->业务逻辑层<--->数据访问层<--->(数据库)
各层之间的调用顺序是固定的,不允许跨层访问。
在mybatis框架结构下应用dao层
新建接口
新建一个包dao用来存储接口类,新建接口类(例如:DeptDao)
添加操作数据库方法
将对应的数据库映射文件中的操作数据库语句的id写成函数名写入接口类中(例如:GradeMapper.xml中的数据库语句写入DeptDao)
1 2 3 4 5 6 7 8 9 10 11 | /*** * 部门管理数据访问接口 */ public interface DeptDao { public Long queryCount(); public List<Dept> queryDept(Dept dept); /** //部门的查询 Long deptCount = session.selectOne("deptSpace.queryCount"); List<Dept> deptList = session.selectList("deptSpace.queryDept", dept); */ } |
更改映射文件
更改映射文件的namespace为接口类的路径(例如:DeptDao和GradeMapper)
1 2 3 | <!-- 在映射文件GradeMapper中--> <!--<mapper namespace= "deptSpace" >--> <mapper namespace= "org.example.dao.DeptDao" > |
更改关联表的映射文件中引用的该映射文件的namespce(例如:GradeMapper和EmpMapper)
1 2 3 4 | <!--在关联表的映射文件EmpMapper中--> <!-- 集合属性映射--> <association property= "dept" column= "deptNo" javaType= "Dept" resultMap= "org.example.dao.DeptDao.deptMap" ></association> |
使用DAO接口调用
在src/main/java/com/example/mybatisdemo/test/MybatisTest类Main中(例如:DeptDao)
1 2 3 4 5 6 7 8 9 10 11 | //使用接口调用 Dept dept= new Dept(); dept.setDeptNo( 1001 ); dept.setDeptName( "财务部" ); dept.setRemark( "" ); DeptDao deptDao = session.getMapper(DeptDao. class ); List<Dept> depts = deptDao.queryDept(dept); System.out.println( "接口的调用" ); for (Dept d : depts){ System.out.println(d.toString()); } |
整合Spring Boot
配置文件
在src/main/resources/application.yml,链接mysql数据库
1 2 3 4 5 6 7 | #链接mysql数据库 spring: datasource: driver- class -name: com.mysql.jdbc.Driver url: jdbc:mysql: //localhost:3307/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC username: root password: 123456 |
Pojo实体函数层
在java的主目录下新建po目录,并添加需要的数据库实体类(例如:Dept和Emp)
和在resource下添加对应的数据库映射文件
Dao(DAL)数据访问层
在java的主目录下新建dao目录,并添加po目录下实体类对应的数据访问接口,
1 2 3 4 5 6 7 8 9 10 | /*** * 部门管理数据访问接口 * 加注解 */ @Mapper public interface DeptDao { public Long queryCount(); public List<Dept> queryEmpByDept(); public List<Dept> queryDept(Dept dept); } |
例如DeptDao:
在src/main/resources/application.yml整合mybatis
1 2 3 4 5 6 7 8 9 | #整合mybatis mybatis: #加载实体类所在的包类 type-aliases- package : springbootdemo.demo.po #加载映射文件 mapper-locations: mapper/*.xml #把日志输出到控制台 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl |
简单地测试数据访问
在src/test/java/springbootdemo/demo/DemoApplicationTests.java添加测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 | //注入mapper @Autowired private DeptDao deptDao; @Test public void testQueryDept(){ List<Dept> list = this .deptDao.queryDept( new Dept()); System.out.println( "测试成功" ); for (Dept d : list){ System.out.println(d.toString()); } } |
点击测试,出结果测试成功
Service(BLL)业务逻辑层
在java的主目录下新建service目录,并添加po目录下实体类对应的业务逻辑接口和业务逻辑实现类
1 2 3 4 5 6 7 8 | /*** * 部门业务逻辑接口 */ public interface DeptService { public Long queryCount(); public List<Dept> queryEmpByDept(); public List<Dept> queryDept(Dept dept); } |
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 | /*** * 部门业务逻辑实现 */ @Service @Transactional public class DeptServiceImpl implements DeptService{ //注入 @Autowired private DeptDao deptDao; //实现方法 //数据库的事务只有增删改,查不参与(特殊处理):Transactional @Override // @Transactional(Propagation.SUPPORTS) @Transactional (readOnly = true ) public Long queryCount() { return this .deptDao.queryCount(); } @Override @Transactional (readOnly = true ) public List<Dept> queryEmpByDept() { return this .deptDao.queryEmpByDept(); } @Override @Transactional (readOnly = true ) public List<Dept> queryDept(Dept dept) { return this .deptDao.queryDept(dept); } } |
Controller(UI)界面控制层(同步)
在java的主目录下新建controller目录,并添加po目录下实体类对应的界面控制类
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 | /** * 同步,客户端和服务器端, * 页面提交请求,整个页面加载完才能做第二次操作, * 整个页面的完整刷新,要有等待 * */ @Controller //异步,页面提交请求,服务器没有响应,依然可以二次操作,局部刷新 //@RestController public class DeptController { //注入 @Autowired private DeptService deptService; //识别方法 @RequestMapping ( "/index" ) //RequestMapping的细化 // @GetMapping("/index") // @PostMapping("/index") public String index(Model model){ List<Dept> list = this .deptService.queryDept( new Dept()); System.out.println( "查询成功" ); for (Dept d : list){ System.out.println(d.toString()); } model.addAttribute( "list" ,list); //返回页面的名称(返回的字符串必须和页面的名称相同) return "index" ; //index.html默认已经添加后缀 } @RequestMapping ( "/query" ) public String query(){ return "list" ; //错误示例 } } |
在src/main/resources/templates中对应的 HTML文件(例如index.html和list.html)
并在其<html>标签中添加 xmlns:th="http://www.thymeleaf.org"建立映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <!DOCTYPE html> <html lang= "en" xmlns:th= "http://www.thymeleaf.org" > <head> <meta charset= "UTF-8" > <title>首页</title> </head> <body> <h1>首页</h1> <table> <tr> <th>编号</th> <th>名称</th> <th>备注</th> </tr> <tr th:each= "dept:${list}" > <td th:text= "${dept.deptNo}" ></td> <td th:text= "${dept.deptName}" ></td> <td th:text= "${dept.remark}" ></td><br> <td><br> <a th:href= "@{/del(deptNo=${dept.deptNo})}" >删除</a><br> <a th:href= '@{/gotoEdit/}+${dept.deptNo}' >修改</a><br> </td> </tr> </table> </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 | <!DOCTYPE html> <html lang= "en" > <head> <meta http-equiv= "Content-Type" content= "text/html;charset=utf-8" > <title>list</title> </head> <body> <p>列表</p> </body> </html> |
最后在java主函数中添加注解
1 | //开启事务管理<br> @EnableTransactionManagement |
Controller(UI)界面控制层(异步)
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 | //异步调用 @RequestMapping ( "/ajaxQuery" ) @ResponseBody @ApiOperation ( "异步查询部门" ) public List<Dept> ajaxQuery(){ List<Dept> list = this .deptService.queryDept( new Dept()); return list; } @RequestMapping ( "/ajaxQueryAll" ) @ResponseBody @ApiOperation ( "异步查询员工" ) public List<Dept> ajaxQueryAll(){ List<Dept> list = this .deptService.queryAllDept( new Dept()); return list; } @RequestMapping ( "/ajaxSaveDept" ) @ResponseBody @ApiOperation ( "异步保存添加部门" ) public String ajaxSaveDept( @RequestBody Dept dept){ this .deptService.addDept(dept); return "success" ; } @RequestMapping ( "/ajaxDelDept/{deptNo}" ) //开启事务管理 @ResponseBody @ApiOperation ( "异步删除部门" ) public String ajaxDelDept( @PathVariable String deptNo){ this .deptService.delDept(Integer.valueOf(deptNo)); return "success" ; } @GetMapping ( "/ajaxQueryDept/{deptNo}" ) @ResponseBody @ApiOperation ( "异步根据编码查询部门" ) public Dept ajaxQueryDept( @PathVariable String deptNo){ Dept dept = new Dept(); dept.setDeptNo(Integer.valueOf(deptNo)); List<Dept> list = this .deptService.queryDept(dept); return list.get( 0 ); } @PostMapping ( "/ajaxUpdateDept" ) @ResponseBody @ApiOperation ( "异步删除部门" ) public String ajaxUpdateDept( @RequestBody Dept dept){ this .deptService.updateDept(dept); return "success" ; } |
在src/config 新建一个CorsFilter类,用作跨域处理
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 | import org.springframework.context.annotation.Configuration; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 跨域过滤器,解决前后端分离时前端项目的跨域请求报错 */ @Configuration public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; HttpServletRequest request = (HttpServletRequest) req; // 不使用*,自动适配跨域域名,避免携带Cookie时失效 String origin = request.getHeader( "Origin" ); if (origin != null && !origin.equals( "" )) { response.setHeader( "Access-Control-Allow-Origin" , origin); } // 自适应所有自定义头 String headers = request.getHeader( "Access-Control-Request-Headers" ); if (headers != null && !headers.equals( "" )) { response.setHeader( "Access-Control-Allow-Headers" , headers); response.setHeader( "Access-Control-Expose-Headers" , headers); } // 允许跨域的请求方法类型 response.setHeader( "Access-Control-Allow-Methods" , "*" ); // 预检命令(OPTIONS)缓存时间,单位:秒 response.setHeader( "Access-Control-Max-Age" , "3600" ); // 明确许可客户端发送Cookie,不允许删除字段即可 response.setHeader( "Access-Control-Allow-Credentials" , "true" ); chain.doFilter(request, response); } } |
配置application文件
更改服务器端口
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 | #链接mysql数据库 #com.mysql.jdbc.Driver高版本数据库链接要添加cj:com.mysql.cj.jdbc.Driver spring: datasource: driver- class -name: com.mysql.cj.jdbc.Driver url: jdbc:mysql: //localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: 123456 mvc: format: date: yyyy-MM-dd jackson: date-format: yyyy-MM-dd time-zone: Asia/Shanghai #整合mybatis mybatis: #加载实体类所在的包类 type-aliases- package : springbootdemo.demo.po #加载映射文件 mapper-locations: mapper/*.xml #把日志输出到控制台 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl server: port: 8888 |
处理跨域请求
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 | import org.springframework.context.annotation.Configuration; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 跨域过滤器,解决前后端分离时前端项目的跨域请求报错 */ @Configuration public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; HttpServletRequest request = (HttpServletRequest) req; // 不使用*,自动适配跨域域名,避免携带Cookie时失效 String origin = request.getHeader( "Origin" ); if (origin != null && !origin.equals( "" )) { response.setHeader( "Access-Control-Allow-Origin" , origin); } // 自适应所有自定义头 String headers = request.getHeader( "Access-Control-Request-Headers" ); if (headers != null && !headers.equals( "" )) { response.setHeader( "Access-Control-Allow-Headers" , headers); response.setHeader( "Access-Control-Expose-Headers" , headers); } // 允许跨域的请求方法类型 response.setHeader( "Access-Control-Allow-Methods" , "*" ); // 预检命令(OPTIONS)缓存时间,单位:秒 response.setHeader( "Access-Control-Max-Age" , "3600" ); // 明确许可客户端发送Cookie,不允许删除字段即可 response.setHeader( "Access-Control-Allow-Credentials" , "true" ); chain.doFilter(request, response); } } |
使用 Swagger测试API
SwaggerConfig配置类
在src/config/目录下新建SwaggerConfig类
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 | import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket getRestApi() { return new Docket(DocumentationType.SWAGGER_2) .pathMapping( "/" ) .select() // 扫描自定义控制器所在的包路径 .apis(RequestHandlerSelectors.basePackage( "com.example.elicense.controller" )) .paths(PathSelectors.any()) // 构建swagger首页的自定义信息 .build().apiInfo( new ApiInfoBuilder() .title( "SpringBoot整合Swagger" ) .description( "SpringBoot整合Swagger,详细信息......" ) .version( "1.0" ) .contact( new Contact( "xphohr" , "http://www.cnblogs.com/linlinmailbox" , "1521470143@qq.com" )) .license( "The Apache License" ) .licenseUrl( "https://www.apache.org/" ) .build()); } } |
添加注解
在每一个视图控制器Controller里添加注解@Api(tags=""), @ApiOperation(""), @ApiImplicitParams({ @ApiImplicitParam(name = "",value = "",dataType = ""),})
例如:
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 97 98 | /** * 视图控制层:元数据 */ @Controller @RequestMapping ( "/item" ) @Api (tags= "元数据管理控制器" ) public class ItemController { //自动装配 业务逻辑层 @Autowired private ItemService itemService; /** * 分页查询 * * @param pageNo * @param pageSize * @return */ @GetMapping ( "/all" ) @ResponseBody @ApiOperation ( "分页查询" ) @ApiImplicitParams ({ @ApiImplicitParam (name = "pageNo" ,value = "页码" ,dataType = "int" ), @ApiImplicitParam (name = "pageSize" ,value = "页面大小" ,dataType = "int" ), }) public List<Item> queryAllItem( @RequestParam int pageNo, @RequestParam int pageSize) { return this .itemService.queryItem(pageNo, pageSize); } /** * 条件查询 * * @param paramsName * @param paramsValue * @return */ @GetMapping ( "/{paramsName}/{paramsValue}" ) @ResponseBody @ApiOperation ( "条件查询" ) @ApiImplicitParams ({ @ApiImplicitParam (name = "paramsName" ,value = "参数名" ,dataType = "String" ), @ApiImplicitParam (name = "paramsValue" ,value = "参数值" ,dataType = "String" ), }) public List<Item> queryItemsByItem( @PathVariable String paramsName, @PathVariable String paramsValue) { return this .itemService.queryItemCondition(paramsName, paramsValue); } /** * 新增元数据 * * @param item * @return */ @PostMapping ( "/add" ) @ResponseBody @ApiOperation ( "新增元数据" ) @ApiImplicitParams ({ @ApiImplicitParam (name = "item" ,value = "元数据信息对象" ,dataType = "Item" ), }) public String addItem( @RequestBody Item item) { this .itemService.insertItem(item); return "success" ; } /** * 修改元数据信息 * * @param item * @return */ @PostMapping ( "/update" ) @ResponseBody @ApiOperation ( "修改元数据信息" ) @ApiImplicitParams ({ @ApiImplicitParam (name = "item" ,value = "模板信息对象" ,dataType = "Item" ), }) public String updateItem( @RequestBody Item item) { this .itemService.updataItem(item); return "success" ; } /** * 删除元数据信息 * * @param itemNo * @return */ @PostMapping ( "/del/{itemNo}" ) @ResponseBody @ApiOperation ( "删除元数据信息" ) @ApiImplicitParams ({ @ApiImplicitParam (name = "itemNo" ,value = "模板编号" ,dataType = "int" ), }) public String delItem( @PathVariable Integer itemNo) { this .itemService.deleteItemByItemNo(itemNo); return "success" ; } } |
未完待续。。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!