框架
一套规范。
实际是他人实现的一系列接口和类的集合。通入导入对应框架的jar文件(maven项目导入对应的依赖),进行适当的配置,就能使用其中的所有内容。
开发者可以省去很多模板代码,如dao中的CRUD,MVC模式下层与层之间的关联。只需要集中精力实现项目中的业务逻辑部分
主流框架
Spring、SpringMVC、MyBatis、MyBatisPlus、Hibernate、JPA等。
SSH:最初是Spring+Stucts2+Hibernate组成,之后Stucts2被SpringMVC取代。
SSM:Spring+SpringMVC+MyBatis
新项目使用SpringBoot,早起的SSH项目由于维护成本高,基本不会推翻重做,但会维护一些SSM项目。
无论是SSH还是SSM,Spring、SpringMVC必不可少。从2004年推出至今,依旧是主流框架中不可获取的一部分。
SPring
一个轻量级开源的Java框架。是一个管理项目中对象的容器,同时也是其他框架的粘合器,目的就是对项目进行解耦。
Spring的核心是IOC控制反转和AOP面向切面编程
Spring 应用
1.创建一个普通的Maven项目,不选择模板
2.添加Spring核心依赖
jdk8用5.x版本
<!-- spring-context表示spring核心容器 -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.23</version>
</dependency>
3.创建一个Java类
package com.hqyj.spring01;
/*
* 定义一个普通的Java类
* PlainOrdinaryJavaObject pojo 相当于简化的javabean
* entity vo dto pojo 都是在描述实体类
* */
public class PlainOrdinaryJavaObject {public void fun(){System.out.println("一个PlainOrdinaryJavaObject(普通的Java类)对象");}
}
4.创建Spring配置文件
在resources目录下,创建一个xml文件,选择spring config,通常命名为application
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--bean标签表示,在Spring容器中,注入某个类的对象--><!--class属性表示要注入哪个类,写类的全限定名(包名+类名)--><!--id表示给类的对象的名称--><bean class="com.hqyj.spring01.PlainOrdinaryJavaObject" id="pojo"></bean></beans>
5.创建main方法所在类
解析Spring配置文件,获取注入的对象。
package com.hqyj.spring01;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {//创建一个自定义类的对象//传统方式创建对象//PlainOrdinaryJavaObject pojo = new PlainOrdinaryJavaObject();//使用spring容器获取对象//创建一个用于解析Spring配置文件的对象,参数为配置文件的名称。实际就是获取Spring容器ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");//获取容器中的对象/*只通过名称获取,返回值为Object类型,需要转换Object obj = context.getBean("pojo");PlainOrdinaryJavaObject pojo=(PlainOrdinaryJavaObject) obj;*///使用这种方式,直接用指定类型接收PlainOrdinaryJavaObject pojo = context.getBean("pojo", PlainOrdinaryJavaObject.class);//能成功调用方法,说明Spring管理了该类的对象pojo.fun();//关闭Spring容器context.close();}
}
bean标签常用属性
属性 | 作用 |
---|---|
class | 定义类的全限定名 |
id | 定义对象的名称 |
lazy-init | 是否为懒加载。默认值为false,在解析配置文件时就会创建对象。设置为true表示懒加载,只有在getBean()时才会创建对象。 |
scope | 单例/原型模式。默认值为singleton,表示单例模式,只会创建一个对象。设置为prototype,表示原型模式,每调getBean()就创建一个对象。 |
init-method | 初始化时触发的方法。在创建完该对象时自动调用的方法。该方法只能是无参方法,该属性的值只需要写方法名即可 |
destory-method | 销毁时触发的方法。Spring容器关闭时自动调用的方法,该方法只能是无参方法。只有在单例模式下有效。 |
属性注入
给某个bean添加属性的方式有两种:构造器注入和setter注入
setter注入
这种方式注入属性时,类中必须要有set方法
在bean标签中,加入<property></property>
标签,
该标签的name属性通常表示该对象的某个属性名,但实际是setXXX()方法中的XXX单词。
如有age属性,但get方法为getNianLing(),name属性就需要写成nianLing。
该标签的value属性表示给该类中的某个属性赋值,该属性的类型为原始类型或String。
该标签的ref属性表示给该类中除String以外的引用类型属性赋值,值为Spring容器中另一个bean的id。
<!--注入Car类对象并用set方式注入其属性-->
<bean class="com.hqyj.spring01.Car" id="c"><!--该属性是字符串或原始类型,使用value赋值--><property name="brand" value="宝马"></property><!--name并不是类中是属性名,而是该属性对应的getXXX()方法中XXX的名称--><!--如Car类中有color属性,但get方法名为getColo(),这里就要写为colo--><property name="colo" value="白色"></property>
</bean><!--注入Person类对象并用set方式注入其属性-->
<bean class="com.hqyj.spring01.Person" id="p1"><property name="name" value="王海"></property><property name="age" value="22"></property><!--属性是引用类型,需要通过ref赋值,值为另外的bean的id ref即references--><property name="car" ref="c"></property>
</bean>
构造方法注入
这种方式注入属性时,类中必须要有相应的构造方法
在bean标签中,加入<constructor-arg></constructor-arg>
标签,
该标签的name属性表示构造方法的参数名,index属性表示构造方法的参数索引。
赋值时,原始类型和字符串用value,引用类型用ref。
<!--注入Person类对象并用构造方法注入其属性-->
<bean class="com.hqyj.spring01.Person" id="p2"><!--constructor-arg表示构造方法参数 name是参数名 index是参数索引--><constructor-arg name="name" value="张明"></constructor-arg><constructor-arg index="1" value="20"></constructor-arg><constructor-arg name="car" ref="c"></constructor-arg>
</bean>
复杂属性注入
/*
* 定义电影类
* */
public class Movie {//电影名private String movieName;//导演private String director;//时长private int duration;//主演private List<String> playerList;//类型private String movieType;//放映时间,最终格式为yyyy/MM/dd HH:mm:ssprivate String showTime;
}
List类型的属性
<!--注入Movie对象-->
<bean class="com.hqyj.spring01.Movie" id="movie1"><property name="movieName" value="夏洛特烦恼"></property><property name="director" value="闫非、彭大魔"></property><property name="duration" value="87"></property><!--List类型属性赋值--><property name="playerList" ><!--使用list标签--><list><!--如果集合中保存的是引用类型,使用ref标签--><!--如果集合中保存的是原始类型或字符串,使用value标签--><value>沈腾</value><value>艾伦</value><value>马丽</value></list></property><property name="movieType" value="喜剧"></property><property name="showTime" value="2010/06/01 0:0:0"></property>
</bean>
Set类型的属性
<!--注入PetShop对象-->
<bean class="com.hqyj.spring01.test3.PetShop" id="petShop"><property name="shopName" value="xxx宠物店"></property><property name="petSet"><!--Set类型的属性--><set><!--如果中保存的是原始类型或字符串,使用value子标签--><!--如果保存的是引用类型,使用ref子标签加bean属性设置对应的id--><ref bean="p1"></ref><ref bean="p2"></ref><ref bean="p3"></ref></set></property>
</bean>
Map类型的属性
<!--注入Cinema对象-->
<bean class="com.hqyj.spring01.Cinema" id="cinema"><property name="name" value="万达影城"></property><property name="timeTable"><!--Map类型属性赋值,标明键和值的类型--><map key-type="java.lang.Integer" value-type="com.hqyj.spring01.Movie"><!--entry标签表示键值对 如果键值对都是原始类型或字符串,使用key和value--><!--如果键值对中有引用类型,使用key-ref或value-ref--><entry key="1" value-ref="movie1"></entry><entry key="2" value-ref="movie2"></entry></map></property>
</bean>
属性值如果通过某个方法调用而来
如使用String保存yyyy/MM/dd格式的日期,需要通过SimpleDateFormat对象调用parse()方法而来
<!--SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");-->
<!--注入SimpleDateFormat对象,设置日期格式-->
<bean class="java.text.SimpleDateFormat" id="sdf"><!--如果构造方法注入时,该构造方法只有一个参数,可以不用写name或index--><constructor-arg value="yyyy/MM/dd"></constructor-arg>
</bean>
<!--Date now = new Date();-->
<!--注入Date对象-->
<bean class="java.util.Date" id="now"></bean><!--注入Pet对象-->
<bean class="com.hqyj.spring01.test3.Pet" id="p1"><property name="petType" value="哈士奇"></property><property name="petNickName" value="小哈"></property><!--使用当前时间作为该属性的值,以yyyy/MM/dd--><!--sdf.format(now);--><property name="petBirthday"><!--如果某个值是通过某个bean调用了某个方法而来--><bean factory-bean="sdf" factory-method="format"><!--方法的实际参数--><constructor-arg ref="now"></constructor-arg></bean></property>
</bean>
属性自动注入autowire
以上所有案例中,如果要在A对象中注入一个引用类型的对象B,都是手动将对象B注入到对象A中。
如在Person中注入Car对象,在Cinema中注入Movie等。
这种情况下,如果当某个被注入的bean的id更改后,所有引用了该bean的地方都要进行修改。
所以将手动注入更改为自动注入(自动装配),就无需添加相应的<property>
标签,甚至可以无需定义bean的id。
实现自动注入
在某个bean标签中,加入autowire属性,设置值为"byName"或"byType"。通常设置为"byType"。
Car类
package com.hqyj.spring01.test1;import java.util.HashMap;public class Car {private String brand;private String color;@Overridepublic String toString() {return "Car{" +"brand='" + brand + '\'' +", color='" + color + '\'' +'}';}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}
}
Person类
package com.hqyj.spring01.test1;public class Person {private String name;private int age;private Car car;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", car=" + car +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Car getCar() {return car;}//自动注入时,必须要有该方法//byType方式自动注入,会自动在容器中寻找该方法参数类型//byName方式自动注入,会自动在容器中寻找该方法setCar中的car这个idpublic void setCar(Car car) {this.car = car;}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--在容器中注入Car类型的bean--><bean class="com.hqyj.spring01.test1.Car" id="car"> <property name="brand" value="宝马"></property><property name="colo" value="白色"></property></bean><!--注入Person类的bean--><!--autowire="byType"表示自动检测该类中是否需要使用引用类型参数,如果容器中正好有唯一的一个对应类型的bean,就会自动赋值给对应的属性--><bean class="com.hqyj.spring01.test1.Person" id="person" autowire="byType"><property name="name" value="赵明"></property><property name="age" value="26"></property></bean><!--autowire="byName"表示自动检测该类中的setXXX方法,如果某个setXXX方法的XXX和容器某个对象的id对应,就会自动赋值给对应的属性--><bean class="com.hqyj.spring01.test1.Person" id="person2" autowire="byName"><property name="name" value="王海"></property><property name="age" value="26"></property></bean>
</beans>
Main
package com.hqyj.spring01.test1;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");Person person = context.getBean("person", Person.class);//此时打印时,会输出姓名、年龄和Car对象System.out.println(person);context.close();}
}
autowire属性的值
-
byType
- 类中要有被注入的属性的setter()方法
- 被自动注入的对象可以没有id
- Spring容器中,某个对象的类型要与该setter()方法的参数类型一致,且容器中只有一个该类型的对象。
- 如setCar(Car c),Spring就会自动在容器中寻找类型为Car的对象自动装配
-
byName
- 类中要有被注入的属性的setter()方法
- 被自动注入的对象必须要有id
- 实际是根据setXXX()方法set后的单词XXX关联
- 如setCar(Car c),Spring就会自动在容器中寻找id为car的对象自动装配
在Web项目中,可以利用自动装配,在控制层中自动装配业务逻辑层的对象,在业务逻辑层中自动装配数据访问层的对象。
属性自动注入练习
在电影院案例的基础上
1.创建一个dao层CinemaDao类,包含Cinema对象,定义一个fun()方法,输出Cinema对象
package com.hqyj.spring01.test2.dao;import com.hqyj.spring01.test2.Cinema;/*
* 定义数据访问层
* 模拟查询数据库得到结果
* */
public class CinemaDao {private Cinema cinema;public void setCinema(Cinema cinema) {this.cinema = cinema;}public void fun(){System.out.println(cinema);}
}
2.创建一个controller层CinemaController类,包含CinemaDao对象,定义方法fun()调用CinemaDao中的fun()方法
package com.hqyj.spring01.test2.controller;import com.hqyj.spring01.test2.dao.CinemaDao;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*
* 定义控制层类,该层需要使用下一层dao中的方法
* */
public class CinemaController {//这里无需创建对象,让Spring自动注入CinemaDao对象private CinemaDao cinemaDao;public void setCinemaDao(CinemaDao dao) {this.cinemaDao = dao;}public void fun(){cinemaDao.fun();}
}
最终在Main方法中获取Controller对象后,调用fun()
public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("movie.xml");CinemaController controller = context.getBean("controller", CinemaController.class);controller.fun();
}
配置文件
<!--省略movie对象--><!--注入Cinema对象-->
<bean class="com.hqyj.spring01.test2.Cinema" id="cinema"><property name="name" value="万达影城"></property><property name="timeTable"><!--Map类型属性赋值,标明键和值的类型--><map key-type="java.lang.Integer" value-type="com.hqyj.spring01.test2.Movie"><!--entry标签表示键值对 如果键值对都是原始类型或字符串,使用key和value--><!--如果键值对中有引用类型,使用key-ref或value-ref--><entry key="1" value-ref="movie1"></entry><entry key="2" value-ref="movie2"></entry></map></property>
</bean><!--该类中有一个Cinema类型的属性,这里使用了autowire=byType自动装配,会在容器中寻找Cinema类型的对象自动赋值给属性-->
<bean class="com.hqyj.spring01.test2.dao.CinemaDao" autowire="byType"></bean><!--该类中有一个CinemaDao类型的属性,这里使用了autowire=byType自动装配,会在容器中寻找CinemaDao类型的对象自动赋值给属性-->
<bean class="com.hqyj.spring01.test2.controller.CinemaController" id="controller" autowire="byType"></bean>
核心注解
在Spring配置文件中加入
<!--设置要扫描的包,扫描这个包下所有使用了注解的类-->
<context:component-scan base-package="com.hqyj.spring02.bookSystem"></context:component-scan>
类上加的注解
- @Component
- 当一个类不好归纳时,定义为普通组件
- @Controller
- 定义一个类为控制层组件
- @Service
- 定义一个类为业务层组件
- @Repository
- 定义一个类为持久层(数组访问层)组件
- @Lazy/@Lazy(value=true)
- 设置该类为懒加载。
- @Scope(value=“singleton/prototype”)
- 设置为单例/原型模式。
属性上加的注解
-
@Autowired
-
优先使用byType方式从Spring容器中获取对应类型的对象自动装配。先检索Spring容器中对应类型对象的数量,如果数量为0直接报错;数量为1直接装配
数量大于1,会再尝试使用byName方式获取对应id的对象,但要配合@Qualifier(value=“某个对象的id”)一起使用,指定id进行装配
-
-
@Qualifier(value=“某个对象的id”)
- 配合@Autowired注解,使用byName方式获取某个对象id的bean进行装配
-
@Resource(name=“某个对象的id”)
-
该注解相当于@Autowired+@Qualifier(value=“某个对象的id”)
-
优先使用byName方式,从Spring容器中检索name为指定名的对象进行装配,如果没有则尝试使用byType方式,要求对象有且只有一个,否则也会报错。
-
在Web项目中使用Spring
1.创建基于Maven的web-app项目
2.添加依赖
<!--servlet-->
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version>
</dependency>
<!--spring容器-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.23</version>
</dependency>
<!--web集成spring-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.3.23</version>
</dependency>
3.在main目录下创建java和resources目录,修改web.xml版本为4.0
4.在resources目录下创建Spring配置文件application.xml,扫描使用了注解的根包
<?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"><!--扫描使用了Spring注解的根包--><context:component-scan base-package="com.hqyj.springweb"></context:component-scan>
</beans>
5.创建一个类,使用@Componet注解将其注入到Spring容器中
package com.hqyj.springweb.entity;import org.springframework.stereotype.Component;@Component
public class Pojo {public void fun(){System.out.println("hello springweb!");}
}
6.在web.xml中配置监听器用于初始化Spring容器
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!--配置监听器ContextLoaderListener--><listener><!--监听器全限定名--><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--定义全局参数contextConfigLocation用于读取Spring配置文件--><context-param><!--参数名固定contextConfigLocation--><param-name>contextConfigLocation</param-name><!--只是Spring配置文件的路径 classpath:表示从根目录出发--><param-value>classpath:application.xml</param-value></context-param>
</web-app>
7.创建一个Servlet,访问该Servlet,获取Spring容器,从容器中获取注入的对象
package com.hqyj.springweb.controller;import com.hqyj.springweb.entity.Pojo;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@WebServlet("/hello")
public class MyServlet extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//获取Spring容器WebApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(getServletContext());//从容器中获取某个beanPojo pojo = app.getBean("pojo", Pojo.class);pojo.fun();}
}
在Spring-web项目中使用Spring-jdbc
Spring提供了一套自己的JDBC解决方案,相较于自己写的JDBC,省去了繁琐的过程(获取连接、关闭),简化了对数据库的操作。
Spring-jdbc后续会用其他持久层框架代替,如MyBatis、JPA。
1.引入相关依赖
当前案例中核心的依赖是 spring-jdbc
<!--servlet-->
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version>
</dependency>
<!--mysql-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.31</version>
</dependency>
<!--jstl-->
<dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId><version>1.2</version>
</dependency>
<!--spring容器-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.23</version>
</dependency>
<!--web集成spring-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.3.23</version>
</dependency>
<!-- spring-jdbc -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.23</version>
</dependency>
2.创建包结构(controller、service、dao、entity),创建实体类
public class Hero {private int id;private String name;private String position;private String sex;private int price;private String shelfDate;//省略get/set、构造方法、toString等
}
3.在Spring配置文件中配置数据源和JDBC模板
<?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"><!--扫描使用了Spring注解的根包--><context:component-scan base-package="com.hqyj.springweb"></context:component-scan><!--配置数据源--><bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource"><!--配置MySQL驱动--><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property><!--配置MySQL地址--><property name="url" value="jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai"></property><property name="username" value="root"></property><property name="password" value="root"></property></bean><!--配置JDBC模板--><bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate"><!--设置要操作的数据源--><property name="dataSource" ref="dataSource"></property></bean>
</beans>
4.数据访问层(dao)的写法
package com.hqyj.springweb.dao;import com.hqyj.springweb.entity.Hero;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
public class HeroDao {@Autowired//通过jdbc模板对象实现CRUDprivate JdbcTemplate jdbcTemplate;/** 查询所有* */public List<Hero> queryAll() {//定义sql语句String sql = "select * from hero";//BeanPropertyRowMapper对象表示将查询到的字段与指定的类中的属性进行映射BeanPropertyRowMapper<Hero> mapper = new BeanPropertyRowMapper<>(Hero.class);//query()方法执行查询List<Hero> list = jdbcTemplate.query(sql, mapper);return list;}/** 删除* */public boolean delete(int id) {String sql = "delete from hero where id=?";int i = jdbcTemplate.update(sql, id);return i > 0;}/** 修改* */public boolean update(Hero hero) {String sql = "update hero set name=?,sex=?,position=?,price=?,shelf_date=? where id=?";int i = jdbcTemplate.update(sql, hero.getName(), hero.getSex(), hero.getPosition(), hero.getPrice(), hero.getShelfDate(), hero.getId());return i > 0;}/** 添加* */public boolean insert(Hero hero) {String sql = "insert into hero values(null,?,?,?,?,?)";int i = jdbcTemplate.update(sql, hero.getName(), hero.getSex(), hero.getPosition(), hero.getPrice(), hero.getShelfDate());return i > 0;}/** 根据id查询* */public Hero findById(int id) {String sql = "select * from hero where id=?";BeanPropertyRowMapper<Hero> mapper = new BeanPropertyRowMapper<>(Hero.class);Hero hero = jdbcTemplate.queryForObject(sql, mapper, id);return hero;}
}
忽略后续service、servlet、jsp等;
JDBCTemplate常用方法
方法 | 作用 | 说明 |
---|---|---|
query(String sql,RowMapper mapper) | 无条件查询 | 返回值为List集合 |
update(String sql) | 无条件更新(删除、修改) | 返回值为受影响的行数 |
query(String sql,RowMapper mapper,Object… objs) | 条件查询 | 可变参数为?的值 |
update(String sql,Object… objs) | 条件更新(增加、删除、修改) | 可变参数为?的值 |
queryForObject(String sql,RowMapper mapper) | 无条件查询单个对象 | 返回值为指定对象 |
queryForObject(String sql,RowMapper mapper,Object… objs) | 条件查询单个对象 | 返回值为指定对象 |
execute(String sql) | 执行指定的sql | 无返回值 |
SpringMVC
在Web阶段中,控制层是由Servlet实现,传统的Servlet,需要创建、重写方法、配置映射。使用时极不方便,SpringMVC可以替换Servlet。
SpringMVC是Spring框架中位于Web开发中的一个模块,是Spring基于MVC设计模式设计的轻量级Web框架。
SpringMVC提供了一个DispatcherServlet的类,是一个Servlet。它在指定映射(通常设置为/或*.do)接收某个请求后,调用相应的模型处理得到结果,再通过视图解析器,跳转到指定页面,将结果进行渲染。
原理大致为:配置SpringMVC中的DispatcherServlet,将其映射设置为/或.do。*
如果是/表示一切请求先经过它,如果是*.do表示以.do结尾的请求先经过它,
它对该请求进行解析,指定某个Controller中的某个方法,这些方法通常返回一个字符串,
这个字符串是一个页面的名称,再通过视图解析器,将该字符串解析为某个视图的名称,跳转到该视图页面。
使用SpringMVC
1.创建webapp项目
- 修改web.xml版本为4.0
- 创建java和resources目录
- 创建包结构
2.添加依赖
<!-- spring-webmvc -->
<!-- 这个依赖会包含spring-web和spring-context -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.23</version>
</dependency>
3.配置初始化Spring
-
在resources目录下创建Spring配置文件application.xml
<?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"><!--设置扫描使用了注解的根包--><context:component-scan base-package="com.hqyj.springmvc"></context:component-scan> </beans>
-
在web.xml中使用监听器ContextLoaderListener初始化Spring
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!--配置全局监听器初始化Spring--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--定义全局参数读取Spring配置文件--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:application.xml</param-value></context-param> </web-app>
4.配置SpringMVC
-
在resources目录下创建配置SpringMVC的配置文件springmvc.xml
- 配置要扫描的控制层类所在的包名
- 配置内部资源视图解析器以及视图路径的前后缀
<?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"><!--扫描控制层所在的包--><context:component-scan base-package="com.hqyj.springmvc.controller"></context:component-scan><!--配置内部资源视图解析器--><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--最终控制层跳转的页面所在的路径及页面自身后缀名--><!--jsp页面不建议直接通过浏览器访问。在WEB-INF目录下在资源,无法通过浏览器直接方法,所以将jsp保存在WEB-INF目录下,最好创建一个pages--><property name="prefix" value="/WEB-INF/pages/"></property><!--现阶段使用jsp输出数据,所以后缀为.jsp--><property name="suffix" value=".jsp"></property></bean> </beans>
-
在web.xml中配置DispatcherServlet
- 将该Servlet的请求映射设置为/,表示所有请求都会访问该Servlet,由该Servlet再进行分发
<!--配置DispatcherServlet--> <servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!--设置该Servlet的初始化参数,用于读取SpringMVC配置文件--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc.xml</param-value></init-param> </servlet> <servlet-mapping><servlet-name>dispatcherServlet</servlet-name><!--设置该servlet的映射为/或*.do--><url-pattern>/</url-pattern> </servlet-mapping>
5.在WEB-INF目录下创建一个pages目录,在其中创建一个welcome.jsp页面
- 通常jsp页面不允许被浏览器直接访问,需要保存在WEB-INF目录下
6.编写控制层代码
-
在controller包下创建一个类,加上@Controller注解
-
该类中定义的方法方法加入@RequestMapping()注解表示访问该方法的映射
-
该类中定义的方法返回值通常为字符串,表示某个页面的名称,也可以是另一个controller中的方法映射名
package com.hqyj.springmvc.controller;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;//加入@Controller注解表示该类是一个控制层类,替换之前的servlet @Controller //该注解如果只设置请求映射,直接填字符串 @RequestMapping("/first") public class HelloController {//该注解如果还有其他参数要设置,路径用path赋值@RequestMapping(path="/hello")public String hello(){//返回的字符串是某个页面的名称或另一个控制层中方法的请求映射return "welcome";} }
将项目部署到tomcat后,访问http://localhost:8080/SpringMVC_war_exploded/first/hello,即可跳转到指定页面
SpringMVC相关注解
-
@Controller
- 只能写在类上,表示该类属于一个控制器
-
@RequestMapping(“/请求映射名”)/@RequestMapping(value=“/请求映射名”)/@RequestMapping(path=“/请求映射名”)
- 该注解可以写在类或方法上。写在类上用于区分功能模块,写在类上用于区分具体功能
- 默认写一个属性或value或path后的值,都表示访问该类或该方法时的请求映射
-
@RequestMapping(value=“/请求映射名”,method=RequestMethod.GET/POST/PUT/DELETE)
- method属性表示使用哪种请求方式访问该类或该方法
- 如果注解中不止一个属性,每个属性都需要指定属性名
-
**@GetMapping(“/请求映射名”)**相当于@RequestMapping(value=“/请求映射名”,method=RequestMethod.GET)
- post、put、delete同理
- @GetMapping只能写在方法上
-
@PathVariable
-
该注解写在某个方法的某个形参上
-
通常配合@RequestMapping(“/{path}”)获取请求时传递的参数
@RequestMapping("/{path}") public String fun(@PathVariable("path") String pageName){return pageName; } //当前方法如果通过"localhost:8080/项目名/hello"访问,就会跳转到hello.jsp //当前方法如果通过"localhost:8080/项目名/error"访问,就会跳转到error.jsp //映射中的/{path}就是获取路径中的hello或error,将其赋值给形参 //通常用于跳转指定页面
-
-
@RequestParam(value=“传递的参数名”,defaultValue =“没有传递参数时的默认值”)
- 该注解写在某个方法的某个参数上
- 用于获取提交的数据,可以设置默认值在没有提交数据时使用
控制层中获取请求时传递的参数
-
controller中方法的形参名和表单的name或?后的参数名一致
表单或a标签
<form action="${pageContext.request.contextPath}/first/sub"><input type="text" name="username"><input type="text" name="password"><input type="submit" > </form><a href="${pageContext.request.contextPath}/first/sub?username=admin">xxx</a>
controller
public String login(String username,int password){//此时可以正常获取//无关数据类型,但是提交数据时必须是对应的类型,否则会有400错误 }
-
controller中方法的形参名和表单的name或?后的参数名不一致
controller
public String login(@RequestParam(value="username",defaultValue="admin")String name,@RequestParam("username")int pwd){//如果没有提交数据时,会使用设置的默认值 }
-
如果传递的参数都是某个实体类中的属性时
User类
package com.hqyj.springmvc.entity;public class User {private String username;private String password;@Overridepublic String toString() {return "User{" +"username='" + username + '\'' +", password='" + password + '\'' +'}';}public User() {}public User(String username, String password) {this.username = username;this.password = password;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;} }
controller
@RequestMapping("/login") public String login(User user){//某个实体类的属性和提交的参数名一致时,可以直接将对象作为形参,会自动将对应参数赋值给对应属性System.out.println(user);return ""; }
SpringMVC中的跳转
-
控制层跳转到某个jsp页面
-
在控制层中定义方法,这种方式跳转,属于请求转发
-
如果要使用重定向跳转,在页面名之前添加"redirect:"
@RequestMapping("/hello") public String hello(){//返回页面名称return "hello";//请求转发 }@RequestMapping("/hello") public ModelAndView hello(ModelAndView mav){//设置页面名称mav.setViewName("hello");return mav; }
-
在springmvc配置文件中
<mvc:view-controller path="请求映射" view-name="页面名称"></mvc:view-controller> <!-- 访问项目根目录,跳转到welcome.jsp页面 --> <mvc:view-controller path="/" view-name="welcome"></mvc:view-controller> <!-- 这个标签使用时,会让@RequesMapping失效,如果要共存,添加以下标签 --> <!--来自于xmlns:mvc="http://www.springframework.org/schema/mvc" --> <mvc:annotation-driven></mvc:annotation-driven>
-
-
控制层跳转到另一个控制层中的方法
-
方法的返回值为"redirect/forward:另一个控制层中方法的映射名"
@RequestMapping("/hello") public String hello(){return "redirect:hero";//使用重定向的方式,跳转到映射名为hero的控制层return "forward:hero"//使用请求转发的方式,跳转到映射名为hero的控制层 }
-
-
jsp页面跳转另一个jsp页面
-
当前项目中jsp页面都在WEB-INF下,无法直接访问,a标签同样如此,只能通过控制层跳转页面
-
定义用于跳转页面控制层类
package com.hqyj.springmvc.controller;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping;/** 定义一个用于跳转指定页面或controller的控制层* */ @Controller public class ToPageController {/*项目启动时或直接访问根目录,跳转到指定的controller*/@RequestMapping("/")public String toIndex() {return "redirect:/hero/queryAll";}/** 这个方法的作用:会将请求中第一个/后的单词截取出来命名为path赋值给参数page* 如 localhost:8080/web/hero,就会识别出hero,return "hero";* 就会跳转到 /WEB-INF/pages/hero.jsp页面* */@RequestMapping("/{path}")public String toPage(@PathVariable("path") String page) {return page;} }
-
这时在页面中这样跳转
<%--这个路径实际是/项目名/addHero,会截取addHero,跳转到/项目名/WEB-INF/pages/addHero.jsp--%> <a href="${pageContext.request.contextPath}/addHero">添加</a>
-
ORM
Object Relational Mapping
对象关系映射 创建Java类的对象,将其属性和数据库表中的字段名之间构建映射关系。 这样通过操作该类创建的一个对象,就能操作数据库中对应的数据表。 ORM框架就是对象关系映射框架,用于简化JDBC。 主流的ORM框架有Hibernate、JPA、MyBatis、MyBatisPlus等 ## MyBatis
一个半自动化的ORM框架。原本叫做ibatis,后来升级改名为MyBatis。 MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。 使用XML文件或注解的形式,将SQL语句映射为持久层(dao层)中的方法。 官网https://mybatis.org/mybatis-3/zh/index.html
特点
方便 简化JDBC 解除SQL语句与业务代码的耦合 sql和java代码分离,sql写在xml文件中,方便扩展维护。 支持动态SQL 不同的情况下构造不同的SQL 主流 SSM中的M
SSM项目搭建
整体流程
1.创建基于Maven的webapp项目
2.修改web.xml版本为4.0,创建java、resoureces目录、项目包结构、web-inf下的页面目录
3.导入依赖 spring-webmvc mybatis mybatis-spring mysql druid spring-jdbc jstl
4.配置Spring 创建application.xml 扫描项目根包 配置web.xml 设置全局参数: ,读取application.xml文件 设置全局监听器:ContextLoaderListener,初始化Spring容器 5.配置SpringMVC 创建springmvc.xml 扫描控制层所在包 设置内部资源视图解析器:InternalResourceViewResolver,设置前后缀 配置web.xml 设置请求分发器:DispatcherServlet,在其中读取springmvc.xml配置文件 过滤器解决请求中文乱码:CharacterEncodingFilter
6.配置MyBatis 创建mybatis-config.xml(官网模板) 在application.xml中注入 数据库连接池Druid:DruidDataSource SQL会话工厂:SqlSessionFactoryBean 映射扫描配置器:MapperScannerConfigurer
7.执行sql语句 创建dao层接口,定义方法 在resoureces下创建mapper目录创建sql映射文件xx.xml(官网模板),在其中定义sql语句
SSM项目搭建补充
通过db.properties文件保存连接数据库的信息
.properties文件称为属性文件,其中的数据以键值对(key=value)的形式保存
-
在resources目录下新建db.properties文件
DB_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver DB_URL=jdbc:mysql://localhost:3306/bookdb?serverTimezone=Asia/Shanghai DB_USERNAME=root DB_PASSWORD=root
-
在application.xml中读取properties文件
<!--读取properties文件--> <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
-
读取时使用EL表达式访问其中的键
<!--Druid数据源DruidDataSource--> <bean class="com.alibaba.druid.pool.DruidDataSource" id="druidDataSource"><property name="driverClassName" value="${DB_DRIVER_CLASS_NAME}"></property><property name="url" value="${DB_URL}"></property><property name="username" value="${DB_USERNAME}"></property><property name="password" value="${DB_PASSWORD}"></property> </bean>
MyBatis配置文件常用设置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--设置--><settings><!--开启驼峰命名映射--><setting name="mapUnderscoreToCamelCase" value="true"/><!--开启SQL日志--><setting name="logImpl" value="STDOUT_LOGGING"/></settings></configuration>
MyBatis基本增删改查
dao层
package com.hqyj.ssm02.dao;import com.hqyj.ssm02.entity.BookInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
public interface BookInfoDao {//查询所有List<BookInfo> queryAll();//根据id删除int delete(int id);//添加int insert(BookInfo bookInfo);//根据id查询BookInfo findById(int no);//修改时,参数通常为一个完整的修改对象int update(BookInfo bookInfo);//批量删除//分页查询//关键字分页//如果dao层中某个方法不止1个参数,需要给每个参数添加@Param("参数名")注解,给该参数命名//命名后,才能在mybatis的sql映射文件中使用该参数,即#{参数名}//如这里的newPrice,在sql中用#{newPrice}获取int update(@Param("newPrice") int bookPrice,@Param("newNum") int bookNum,@Param("updateId") int bookId);
}
service层
package com.hqyj.ssm02.service;import com.hqyj.ssm02.dao.BookInfoDao;
import com.hqyj.ssm02.entity.BookInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class BookInfoService {@Autowiredprivate BookInfoDao bookInfoDao;public List<BookInfo> queryAll() {return bookInfoDao.queryAll();}public boolean delete(int id) {return bookInfoDao.delete(id) > 0;}public void insert(BookInfo bookInfo) {bookInfoDao.insert(bookInfo);}public BookInfo findById(int no) {return bookInfoDao.findById(no);}public void update(BookInfo bookInfo) {bookInfoDao.update(bookInfo);}
}
controller层
package com.hqyj.ssm02.controller;import com.hqyj.ssm02.entity.BookInfo;
import com.hqyj.ssm02.entity.BookType;
import com.hqyj.ssm02.service.BookInfoService;
import com.hqyj.ssm02.service.BookTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;import java.util.List;@Controller
@RequestMapping("/bookInfo")
public class BookInfoController {@Autowiredprivate BookInfoService bookInfoService;//注入BookTypeService,用于获取所有图书类型@Autowiredprivate BookTypeService bookTypeService;@RequestMapping("/queryAll")public String queryAll(Model model) {List<BookInfo> list = bookInfoService.queryAll();model.addAttribute("list", list);return "bookInfoList";}@RequestMapping("/delete")public String delete(int id) {//如果在页面中传递的参数名和方法的形参名一致,会自动获取数据赋值if (bookInfoService.delete(id)) {//增删改执行后,使用重定向跳转return "redirect:queryAll";}return "error";}@RequestMapping("/insert")//如果表单提交的参数和方法的形参名一致,自动获取并赋值//如果表单提交的所有参数正好是一个实体类对象,可以用对应的实体类对象获取public String insert(BookInfo bookInfo){bookInfoService.insert(bookInfo);return "redirect:queryAll";}@RequestMapping("/findById")//如果表单提交的参数名和方法的参数名不一致,使用@RequestParam("提交的参数名")public String findById(@RequestParam("id") int no,Model model){//查询对应的图书信息BookInfo byId = bookInfoService.findById(no);model.addAttribute("book",byId);//查询所有的图书类型,保存到请求中List<BookType> bookTypeList = bookTypeService.queryAll();model.addAttribute("btList",bookTypeList);return "bookEdit";}@RequestMapping("/update")public String update(BookInfo bookInfo){bookInfoService.update(bookInfo);return "redirect:queryAll";}}
sql映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hqyj.ssm02.dao.BookInfoDao"><!--查询所有--><select id="queryAll" resultType="com.hqyj.ssm02.entity.BookInfo">select *from book_info</select><!--根据id查询--><select id="findById" resultType="com.hqyj.ssm02.entity.BookInfo">select *from book_infowhere book_id = #{no}</select><!--根据id删除--><delete id="delete">deletefrom book_infowhere book_id = #{id}</delete><!--添加--><insert id="insert">insert into book_infovalues (null, #{typeId}, #{bookName}, #{bookAuthor}, #{bookPrice}, #{bookNum}, #{publisherDate}, #{bookImg})</insert><!--修改--><update id="update">update book_infoset book_price=#{newPrice},book_num=#{newNum},type_id=#{typeId}where book_id = #{updateId}</update></mapper>
使用BootStrap渲染数据
下载bootstrap的文件夹和jquery,保存到webapp根目录下。
由于在web.xml中,SpringMVC的核心类DispatcherServlet(请求分发器)的映射设置成了"/",表示所有请求,包含静态资源的请求都会交给SpringMVC处理。
解决无法引入静态资源的问题
-
在webapp目录下,新建一个目录,通常命名为static。将项目中的静态资源文件都保存于此。
-
在springmvc.xml中
<!--映射静态资源目录--> <!--location表示要映射的静态资源目录--> <!--mapping表示最终通过哪种方式进行访问。这里表示只要以/static开头的请求,都可以访问静态资源目录--> <mvc:resources mapping="/static/**" location="/static/"></mvc:resources> <!--开启注解驱动,有这个标签,SpringMVC就能区分哪个是资源文件,哪个是Controller--> <mvc:annotation-driven></mvc:annotation-driven>
导入BootStrap的样式和JS文件
<%--导入bootstrap的css文件--%>
<link href="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/css/bootstrap.min.css" rel="stylesheet"type="text/css">
<%--导入jquery--%>
<script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js"></script>
<%--导入boostrap的js文件--%>
<script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script>
创建顶部导航页面
每个页面都需要这三句话,为了方便起见,给每个页面添加顶部导航页面top.jsp
这样其他页面只需要通过<jsp:include>
导入该页面的同时,使用BootStrap的样式和JS文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>Title</title><%--导入bootstrap的css文件--%><link href="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/css/bootstrap.min.css" rel="stylesheet"type="text/css"><%--导入jquery--%><script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js"></script><%--导入boostrap的js文件--%><script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script></head><body><nav class="navbar navbar-default"><div class="container-fluid"><!-- Brand and toggle get grouped for better mobile display --><div class="navbar-header"><button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand" href="#">Brand</a></div><!-- Collect the nav links, forms, and other content for toggling --><div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"><ul class="nav navbar-nav"><li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li><li><a href="#">Link</a></li><li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a><ul class="dropdown-menu"><li><a href="#">Action</a></li><li><a href="#">Another action</a></li><li><a href="#">Something else here</a></li><li role="separator" class="divider"></li><li><a href="#">Separated link</a></li><li role="separator" class="divider"></li><li><a href="#">One more separated link</a></li></ul></li></ul><form class="navbar-form navbar-left"><div class="form-group"><input type="text" class="form-control" placeholder="Search"></div><button type="submit" class="btn btn-default">Submit</button></form><ul class="nav navbar-nav navbar-right"><li><a href="#">Link</a></li><li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a><ul class="dropdown-menu"><li><a href="#">Action</a></li><li><a href="#">Another action</a></li><li><a href="#">Something else here</a></li><li role="separator" class="divider"></li><li><a href="#">Separated link</a></li></ul></li></ul></div><!-- /.navbar-collapse --></div><!-- /.container-fluid --></nav></body>
</html>
列表页面
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<%--使用jsp动作包含一个子页面,也能使用其中的css和js文件--%>
<jsp:include page="top.jsp"></jsp:include>
<div class="row" style="width: 100%"><div class="col-md-2"></div><div class="col-md-8"><table class="table-striped table"><tr><th>图书编号</th><th>类型编号</th><th>图书名称</th><th>图书作者</th><th>图书价格</th><th>图书库存</th><th>出版时间</th><th >操作</th><th><a href="${pageContext.request.contextPath}/bookAdd" class="btn btn-default btn-sm">添加</a></th></tr><c:forEach var="book" items="${list}"><tr><td>${book.bookId}</td><td>${book.typeId}</td><td>${book.bookName}</td><td>${book.bookAuthor}</td><td>${book.bookPrice}</td><td>${book.bookNum}</td><td>${book.publisherDate}</td><td><a href="${pageContext.request.contextPath}/bookInfo/findById?id=${book.bookId}" class="btn btn-primary btn-sm">编辑</a></td><td><a href="${pageContext.request.contextPath}/bookInfo/delete?id=${book.bookId}" class="btn btn-danger btn-sm">删除</a></td></tr></c:forEach></table></div><div class="col-md-2"></div>
</div>
</body>
</html>
详情页面
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>Title</title></head><body><jsp:include page="top.jsp"></jsp:include><div class="row"><div class="col-md-3"></div><div class="col-md-6"><form class="form-horizontal" action="${pageContext.request.contextPath}/bookInfo/update" method="post"><%--隐藏域提交id--%><input type="hidden" name="bookId" value="${book.bookId}"><div class="form-group"><label class="col-sm-2 control-label">图书名称</label><div class="col-sm-10"><input type="text" readonly class="form-control" value="${book.bookName}" name="bookName" placeholder="请输入图书名称" required></div></div><div class="form-group"><label class="col-sm-2 control-label">图书作者</label><div class="col-sm-10"><input type="text" readonly class="form-control" value="${book.bookAuthor}" name="bookAuthor" placeholder="请输入图书作者" required></div></div><div class="form-group"><label class="col-sm-2 control-label">图书类型</label><div class="col-sm-10"><select class="form-control" name="typeId" ><%--遍历所有的图书类型--%><c:forEach items="${btList}" var="bt"><option value="${bt.typeId}" ${bt.typeId==book.typeId?"selected":""}>${bt.typeName}</option></c:forEach></select></div></div><div class="form-group"><label class="col-sm-2 control-label">图书价格</label><div class="col-sm-10"><input type="number" min="1" class="form-control" value="${book.bookPrice}" name="bookPrice" placeholder="请输入图书价格" required></div></div><div class="form-group"><label class="col-sm-2 control-label">图书库存</label><div class="col-sm-10"><input type="number" min="1" class="form-control" value="${book.bookNum}" name="bookNum" placeholder="请输入图书库存" required></div></div><div class="form-group"><label class="col-sm-2 control-label">出版时间</label><div class="col-sm-10"><input type="date" readonly class="form-control" value="${book.publisherDate}" name="publisherDate" required></div></div><div class="form-group"><div class="col-sm-offset-2 col-sm-10"><button type="submit" class="btn btn-default">修改</button></div></div></form></div><div class="col-md-3"></div></div></body>
</html>
添加页面
<%--Created by IntelliJ IDEA.User: AdministratorDate: 2023/1/30Time: 14:17To change this template use File | Settings | File Templates.--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>Title</title></head><body><jsp:include page="top.jsp"></jsp:include><div class="row"><div class="col-md-3"></div><div class="col-md-6"><form class="form-horizontal" action="${pageContext.request.contextPath}/bookInfo/insert" method="post"><div class="form-group"><label class="col-sm-2 control-label">图书名称</label><div class="col-sm-10"><input type="text" class="form-control" name="bookName" placeholder="请输入图书名称" required></div></div><div class="form-group"><label class="col-sm-2 control-label">图书作者</label><div class="col-sm-10"><input type="text" class="form-control" name="bookAuthor" placeholder="请输入图书作者" required></div></div><div class="form-group"><label class="col-sm-2 control-label">图书类型</label><div class="col-sm-10"><select class="form-control" name="typeId"></select></div></div><div class="form-group"><label class="col-sm-2 control-label">图书价格</label><div class="col-sm-10"><input type="number" min="1" class="form-control" name="bookPrice" placeholder="请输入图书价格" required></div></div><div class="form-group"><label class="col-sm-2 control-label">图书库存</label><div class="col-sm-10"><input type="number" min="1" class="form-control" name="bookNum" placeholder="请输入图书库存" required></div></div><div class="form-group"><label class="col-sm-2 control-label">出版时间</label><div class="col-sm-10"><input type="date" class="form-control" name="publisherDate" required></div></div><div class="form-group"><div class="col-sm-offset-2 col-sm-10"><button type="submit" class="btn btn-default">添加</button></div></div></form></div><div class="col-md-3"></div></div></body><script>/*使用ajax查询所有图书类型在页面中使用ajax访问后端如果要得到数据,该数据必须是JSON格式*/$.ajax({//请求地址url: "${pageContext.request.contextPath}/bookType/queryAllToJson",//访问成功后的回调函数success: function (res) {//这里的res是所有类型对象的集合for (var i = 0; i < res.length; i++) {var $opt = $("<option value='" + res[i].typeId + "'>" + res[i].typeName + "</option>")$("select[name=typeId]").append($opt);}}});</script></html>
在SpringMVC中,让某个控制层中的方法返回JSON格式的数据
-
添加依赖
<!--jackson:将数据转换为JSON格式--> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.14.2</version> </dependency>
-
@ResponseBody注解
该注解可以加在类或方法上
- 如果加在方法上,表示该方法的返回值类型为JSON格式
- 如果加在类上,表示该类中的所有方法的返回值类型为JSON格式
package com.hqyj.ssm02.controller;import com.hqyj.ssm02.entity.BookType; import com.hqyj.ssm02.service.BookTypeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;import java.util.List;@Controller @RequestMapping("/bookType") public class BookTypeController {@Autowiredprivate BookTypeService bookTypeService;//如果controller的某个方法返回一个JSON字符串,需要使用@ResponseBody@RequestMapping("/queryAllToJson")@ResponseBody//表示该方法无论返回值是什么,都返回JSON格式字符串public List<BookType> queryAllToJson(){List<BookType> list = bookTypeService.queryAll();return list;} }
SSM项目中使用Ajax
ajax依赖于jquery,所以先保证页面中存在jquery.js。
$.ajax({url:"访问地址",data:{"提交的参数名":"实际值","提交的参数名":"实际值"},type:"get/post",success:function(res){//成功后的回调函数,res为访问后的结果,必须是json格式}
});
在前端页面中使用ajax访问controller时,controller的返回值必须是一个JSON格式的字符串。
所以controller中的方法上要加入@ResponseBody注解
使用Aajx实现注册和登录
表
当前表为customer,包含字段如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qo1GsDU8-1676548026833)(D:/qqFileRecv/框架/框架/SSM03.assets/image-20230131154742977.png)]
使用该表中的phone和password作为登录时的账户和密码
实体类
package com.hqyj.ssm02.entity;
/*
* 对应customer表
* */
public class SysAdmin {//用户名adminName属性对应phone字段private String adminName;//密码adminPwd属性对应password字段private String adminPwd;//省略get/set/toString
}
注册
注册时先判断用户名是否存在,如果存在则不能注册
-
dao
/*查询用户名是否存在,返回查询到的数量*/ int findByAdminName(String adminName); /*添加*/ int insert(SysAdmin sysAdmin);
-
mapper.xml
<!--查询用户名(phone)是否存在--> <select id="findByAdminName" resultType="java.lang.Integer">select count(*)from customerwhere phone = #{adminName} </select><!--添加用户--> <insert id="insert">insert into customervalues (null, #{adminName}, #{adminPwd}, 0, null) </insert>
-
service
/** 检查注册的用户名是否存在* */ public boolean findAdminName(SysAdmin sysAdmin) {//查询要注册的用户名是否存在int i = sysAdminDao.findByAdminName(sysAdmin.getAdminName());if (i != 0) {return false;}return true; }/** 注册* */ public boolean register(SysAdmin sysAdmin) {return sysAdminDao.insert(sysAdmin) > 0; }
-
controller
/** 查询名称是否存在,返回boolean类型的json字符串* */ @RequestMapping("/findAdminName") @ResponseBody public boolean findAdminName(SysAdmin sysAdmin) {return sysAdminService.findAdminName(sysAdmin); }/** 注册成功后跳转到登录页面* */ @RequestMapping("/register") public String register(SysAdmin sysAdmin) {sysAdminService.register(sysAdmin);return "login"; }
-
页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html><head><title>Title</title><style>.cus-portrait {margin: 0 auto;width: 100px;height: 100px;border: 1px solid #337ab7;text-align: center;line-height: 100px;border-radius: 50px;}</style></head><body><jsp:include page="top.jsp"></jsp:include><div class="col-md-4"></div><div class="col-md-4"><div class="panel panel-primary"><div class="panel-heading">用户注册</div><div class="panel-body"><form action="${pageContext.request.contextPath}/sysAdmin/register" method="post"><div class="form-group"><label class="warning">用户名</label><input type="text" class="form-control" required name="adminName" placeholder="请输入用户名"></div><div class="form-group"><label>密码</label><input type="password" class="form-control" required name="adminPwd" placeholder="请输入密码"></div><button type="submit" class="btn btn-default">注册</button></form></div></div></div><div class="col-md-4"></div><script>$(function () {//用户名文本框失去焦点触发$("input[name=adminName]").blur(function () {$.ajax({//访问controllerurl:"${pageContext.request.contextPath}/sysAdmin/findAdminName",data:{//提交数据"adminName":$(this).val()},success:function(res){//res是true、false。true表示该用户名不存在if(res){$(".warning").text("√").css("color","green");$("button[type=submit]").removeAttr("disabled");}else{$(".warning").text("该用户名已存在").css("color","red");$("button[type=submit]").attr("disabled","disabled");}}});});});</script></body> </html>
登录
-
dao
/*登录*/ SysAdmin login(SysAdmin sysAdmin);
-
mapper.xml
<!--登录--> <!--如果查询的字段名和实体的属性名不一致,需要自定义查询结果集映射--> <select id="login" resultMap="loginMap">select *from customerwhere phone = #{adminName}and password = #{adminPwd} </select> <!--自定义返回结果集映射--> <resultMap id="loginMap" type="com.hqyj.ssm02.entity.SysAdmin"><!--表中的phone字段对应SysAdmin对象的adminName字段--><result property="adminName" column="phone"></result><result property="adminPwd" column="password"></result> </resultMap>
-
service
/** 登录* */ public SysAdmin login(SysAdmin sysAdmin){return sysAdminDao.login(sysAdmin); }
-
controller
@RequestMapping("/login") @ResponseBody public SysAdmin login(SysAdmin sysAdmin,Model model) {SysAdmin login = sysAdminService.login(sysAdmin);//将登录成功的对象保存到session中model.addAttribute("sysAdmin",login);return login; }
-
页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html><head><title>Title</title></head><body><jsp:include page="top.jsp"></jsp:include><div class="col-md-4"></div><div class="col-md-4"><div class="panel panel-primary"><div class="panel-heading">用户登录</div><div class="panel-body"><div class="form-group"><label>用户名</label><input type="text" class="form-control" required id="name" placeholder="请输入用户名"></div><div class="form-group"><label>密码</label><input type="password" class="form-control" required id="pwd" placeholder="请输入密码"></div><div class="form-group"><label class="warning">验证码</label><span class="vcode"></span><input type="text" class="form-control inpVcode" required placeholder="请输入验证码"></div><a class="btn btn-default loginBtn">登录</a><a class="btn btn-default" href="">注册</a></div></div></div><div class="col-md-4"></div><script>//1000~9999var vcode = Math.floor(Math.random() * 8999 + 1000);//显示验证码$(".vcode").text(vcode).css("font-weight", "bolder");//定义一个boolean值用于提交数据时判断验证码是否有误,默认falsevar goon=false;//bind("绑定事件名",满足时触发的函数) input propertychange表示监听文本框输入事件(只要内容有变化就触发)$(".inpVcode").bind("input propertychange", function () {if (vcode != $(this).val()) {//如果验证码有误,改变警告文字$(".warning").text("验证码输入错误").css("color","#f00");}else{//如果输入正确,改变boolean值为true$(".warning").text("√").css("color","#0f0");goon=true;}});//登录按钮单击$(".loginBtn").click(function () {//判断验证码if (!goon) {return;}//使用ajax提交数据进行登录$.ajax({url:"${pageContext.request.contextPath}/sysAdmin/login",data:{"adminName":$("#name").val(),"adminPwd":$("#pwd").val()},success:function (res){if(res!=""){location.href="${pageContext.request.contextPath}/bookInfo/queryAll";}else{alert("用户名或密码错误");}}});});</script></body> </html>
在SpringMVC中使用Session
方式一:@SessionAttributes注解
由于SSM项目中,没有使用servlet,所以不能通过request.getSession()方法来获取session对象。
在控制器Controller中,在类上加入@SessionAttributes注解
**@SessionAttributes({“参数1”,“参数2”})**表示在session中保存两个参数
再在某个方法中,通过Model对象调用addAttribute(“参数1”,对象)方法将指定对象保存到session中
使用和销毁
@Controller
@RequestMapping("/sysAdmin")
//如果要将数据保存到session中,先使用该注解定义session中的参数名
@SessionAttributes({"sysAdmin"})
public class SysAdminController {@AutowiredSysAdminService sysAdminService;@RequestMapping("/login")@ResponseBodypublic SysAdmin login(SysAdmin sysAdmin,Model model) {SysAdmin login = sysAdminService.login(sysAdmin);//将登录成功的对象保存到session中model.addAttribute("sysAdmin",login);return login;}/*登出时销毁session*/@RequestMapping("/logout")public String logout(SessionStatus sessionStatus) {//在方法中使用SessionStatus参数表示session状态对象//调用setComplete()方法,将session设置为完成状态sessionStatus.setComplete();return "redirect:/login";}
}
方式二:HttpSession参数
给项目添加javax.servlet.api依赖,给controller中某个方法添加HttpSession参数后,获取session使用
<!--如果要使用servlet相关内容-->
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version>
</dependency>
使用和销毁
@Controller
@RequestMapping("/sysAdmin")
//如果要将数据保存到session中,先使用该注解定义session中的参数名
@SessionAttributes({"sysAdmin"})
public class SysAdminController {@AutowiredSysAdminService sysAdminService;@RequestMapping("/login")@ResponseBodypublic SysAdmin login(SysAdmin sysAdmin,HttpSession session) {SysAdmin login = sysAdminService.login(sysAdmin);//将登录成功的对象保存到session中session.setAttribute("sysAdmin",login);return login;}/*登出时销毁session*/@RequestMapping("/logout")public String logout(HttpSession session) {session.invalidate();return "redirect:/login";}
}
拦截器
每次请求controller时,都要经过的一个类。
当一个项目中有过滤器、拦截器和controller时的执行流程
实现过程
1.导入servlet依赖
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version>
</dependency>
2.自定义一个类,实现拦截器HandlerInterceptor接口
其中有三个default方法可以重写
- preHandle
- 在发送请求后,DispatcherServlet解析控制器中某个RequestMapping之前执行的方法
- 该方法返回true时,请求才能继续
- postHandle
- 在preHandle方法返回值为true后执行
- 在DispatcherServlet解析控制器中某个RequestMapping之后执行
- afterCompletion
- 在preHandle方法返回true后执行
- 在解析视图后执行的方法
这里只需重写preHandle方法即可
package com.hqyj.ssm02.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*
* 自定义拦截器,用户拦截未登录时的请求
* */
public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String requestURI = request.getRequestURI();//登录成功后,会在session中保存一个名为sysAdmin的对象Object sysAdmin = request.getSession().getAttribute("sysAdmin");//如果有对象,说明登录成功,可以放行return trueif(sysAdmin!=null){return true;}else{response.sendRedirect(request.getContextPath()+"/login");}System.out.println(requestURI+"试图访问,拦截成功");return false;}
}
3.在springmvc.xml中配置拦截器
<!--配置拦截器们-->
<mvc:interceptors><!--配置某个拦截器--><mvc:interceptor><!--设置要拦截的请求,这里的/**表示拦截一切请求--><mvc:mapping path="/**"/><!--设置不拦截的请求--><!--放行登录和注册页--><mvc:exclude-mapping path="/login"/><mvc:exclude-mapping path="/register"/><!--放行静态资源--><mvc:exclude-mapping path="/static/**"/><!--放行用户模块--><mvc:exclude-mapping path="/sysAdmin/**"/><!--注入指定的拦截器--><bean class="com.hqyj.ssm02.interceptor.MyInterceptor"></bean></mvc:interceptor>
</mvc:interceptors>
单元测试
1.导入依赖
如果只在普通的Maven项目中使用,只需导入该依赖
<!--单元测试-->
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope>
</dependency>
如果要在Spring环境下使用,还需
<!--spring集成JUnit-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.23</version><scope>test</scope>
</dependency>
2.创建单元测试类所在目录
在项目的src目录下新建test目录,会自动在其中创建java目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tng8Abg4-1676548026834)(D:/qqFileRecv/框架/框架/SSM04.assets/image-20230201142036858.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qTD9QJb6-1676548026835)(D:/qqFileRecv/框架/框架/SSM04.assets/image-20230201142055759.png)]
3.使用
- 在test/java目录下,新建一个类
- 在类上加入
- @ContextConfiguration(“classpath:application.xml”)
- @RunWith(SpringJUnit4ClassRunner.class)
- 在类中创建公开的无返回值的无参数方法,加入@Test注解
- 在该方法上右键运行,运行的是当前方法,在类中空白处右键运行,运行的是当前类中的所有方法。
import com.hqyj.ssm02.dao.BookInfoDao;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;//解析Spring配置文件
@ContextConfiguration("classpath:application.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class Test {/** 单元测试的方法必须是无参数无返回值的公共方法* */@org.junit.Testpublic void fun(){System.out.println("单元测试");}@Autowiredprivate BookInfoDao bookInfoDao;@org.junit.Testpublic void test1(){System.out.println(bookInfoDao.queryAll());}
}
MyBatis整理
使用前提
-
搭建好MyBatis的环境
-
mybatis配置文件模板
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration></configuration>
-
-
根据表创建实体类
-
默认属性名要和字段名一致
-
如果表中的字段名使用下划线分割单词,实体属性使用驼峰命名法。在mybatis的配置文件中加入开启驼峰命名方式映射
<!--设置--> <settings><!--开启驼峰命名映射--><setting name="mapUnderscoreToCamelCase" value="true"/><!--开启SQL日志--><setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
-
如果表中字段名和实体的属性名既不一致,也不是驼峰命名,就要在写sql语句时,指定字段名和属性名的映射关系
-
实体类必须是公共的,其中必须有无参数的构造方法和get/set方法
-
-
数据访问层
- 数据访问层通常是一个接口,可以称为dao层或mapper层,命名时可以是xxDao或xxMapper
-
写接口的sql映射文件
-
命名通常为xxMapper.xml
-
sql映射文件模板
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="数据访问层接口的全限定名"> </mapper>
-
sql映射文件可以放在resources目录下,spring的配置文件application.xml中通过"classpath:具体路径"读取
如保存在resources/mapper下
<!--SqlSessionFactoryBean--> <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory"><!--指定数据源--><property name="dataSource" ref="druidDataSource"></property><!--读取mybatis配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"></property><!--读取sql映射文件--><property name="mapperLocations" value="classpath:mapper/*.xml"></property> </bean>
-
sql映射文件也可能放在数据访问层目录下,即dao包下
IDEA中,如果将xml文件保存在java包下时,默认在target目录(真正运行时的目录)不会编译加载这些文件,要进行设置
在pom.xml中的build标签中加入以下内容,重新加载
<!--解决xml如果放在java包下时无法编译的问题--> <resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource> </resources>
spring的配置文件application.xml中读取路径正确
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory"><!--指定数据源--><property name="dataSource" ref="druidDataSource"></property><!--读取mybatis配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"></property><!--读取sql映射文件--><property name="mapperLocations" value="classpath:com/hqyj/ssm02/dao/mapper/*.xml"></property> </bean>
-
案例
实体类
开启驼峰命名映射,忽略get/set
BookInfo
public class BookInfo {private int bookId;private int typeId;private String bookName;private String bookAuthor;private int bookPrice;private int bookNum;private String publisherDate;private String bookImg;
}
BookType
public class BookType {private int typeId;private String typeName;
}
查询<select></select>
单表查询
dao中的方法
List<BookType> queryAll();
sql映射文件
-
当数据库表中的字段名和实体属性一致或开启了驼峰命名映射,使用resultType
<select id="queryAll" resultType="com.xxx.entity.BookType">select * from book_type </select>
-
当数据库表中的字段名和实体属性不一致,使用resultMap
<select id="queryAll" resultMap="map">select * from book_type </select> <!--自定义结果集映射--> <resultMap id="map" type="com.xxx.entity.BookType"><!--主键使用id标签,其他字段使用result标签--><!--column对应字段名 property对应属性名--><id column="type_id" property="typeId"></id><result column="type_name" property="typeName"></result> </resultMap>
多表查询
多对一查询
多对一表示多个从表实体对应一个主表实体。如多本图书对应一个图书类型。
在从表实体中,额外添加一个属性:对应的主表实体对象
public class BookInfo {private int bookId;private int typeId;private String bookName;private String bookAuthor;private int bookPrice;private int bookNum;private String publisherDate;private String bookImg;//多对一查询,额外添加外键对应的实体对象private BookType bt;
}
dao中的方法
List<BookInfo> queryAll();
sql映射文件
-
关联查询:构建特殊的sql语句
- 适合外键对应的表中字段比较少的情况下使用
<!--sql语句除了查询自身表之外,还要查询关联的表中的字段,将其命名为"实体对象.属性"--> <select id="queryAll" resultType="com.xxx.entity.BookInfo">select bi.*,type_name as 'bt.typeName' from book_info bi,book_type bt where bi.type_id=bt.type_id </select>
-
连接查询(不建议)
<select id="queryAll" resultMap="booksMap">select *from book_info bi,book_type btwhere bi.type_id = bt.type_id </select> <resultMap id="booksMap" type="com.hqyj.ssm02.entity.BookInfo"><!--主键用id标签,其他字段用result标签赋值--><id property="bookId" column="book_id"></id><result property="bookName" column="book_name"></result><result property="typeId" column="type_id"></result><result property="bookAuthor" column="book_author"></result><result property="bookPrice" column="book_price"></result><result property="bookNum" column="book_num"></result><result property="bookImg" column="book_img"></result><result property="publisherDate" column="publisher_date"></result><!--外键对应的实体对象使用association标签,通过property属性对应实体对象名--><association property="bt" ><!--给外键对象属性赋值--><id column="type_id" property="typeId"></id><result column="type_name" property="typeName"></result></association> </resultMap>
-
子查询
- 适合外键对应的表中字段比较多的情况
<!--1.查询从表--> <select id="queryAll" resultMap="booksMap">select * from book_info </select> <!--2.自定义结果集映射,使用type_id进行子查询,下方的sql--> <resultMap id="booksMap" type="com.hqyj.ssm02.entity.BookInfo"><!--由于使用type_id进行了子查询,所以如果要给type_id赋值,需要再次进行映射--><result column="type_id" property="typeId"></result><!--外键对应的实体对象,使用association赋值--><!--如果这里的子查询来自当前映射文件--><association property="bt" column="type_id" select="findTypeByTypeId"></association><!--如果这里的子查询来自于其他dao中的方法--><!--<association property="bookType" column="type_id" select="com.hqyj.ssm02.dao.BookTypeDao.findById"></association>--> </resultMap><!--3.根据type_id查询完整对象--> <select id="findTypeByTypeId" resultType="com.hqyj.ssm02.entity.BookType">select * from book_type where type_id=#{typeId} </select>
一对多查询
一对多表示一个主表实体对应多个从表实体。如一个图书类型对应多本图书。
在主表对应的实体类中,额外添加一个属性:多个从表对象的集合
public class BookType {private int typeId;private String typeName;private List<BookInfo> books;
}
dao中的方法
BookType queryBooksByType(int typeId);
sql映射文件
-
连接查询
<!--一对多查询,方式一:连接查询--> <select id="queryBooksByType" resultMap="testMap">select *from book_info biinner join book_type bt on bi.type_id = bt.type_idwhere bi.type_id = #{typeId} </select> <!--自定义结果集映射--> <resultMap id="testMap" type="com.hqyj.ssm02.entity.BookType"><id property="typeId" column="type_id"></id><result property="typeName" column="type_name"></result><!--集合类型的属性,使用collection标签,使用ofType设置集合中的对象类型--><collection property="books" ofType="com.hqyj.ssm02.entity.BookInfo"><id column="book_id" property="bookId"></id><result column="book_name" property="bookName"></result><result column="type_id" property="typeId"></result><result column="book_author" property="bookAuthor"></result><result column="book_num" property="bookNum"></result><result column="book_price" property="bookPrice"></result><result column="book_img" property="bookImg"></result><result column="publisher_date" property="publisherDate"></result></collection> </resultMap>
-
子查询
<!--一对多查询,方式二:子查询--> <!--1.根据类型编号查询自身表--> <select id="queryBooksByType" resultMap="testMap">select *from book_typewhere type_id = #{typeId} </select> <!--2.设置结果集映射--> <resultMap id="testMap" type="com.hqyj.ssm02.entity.BookType"><id column="type_id" property="typeId"></id><result column="type_name" property="typeName"></result><!--集合对象,使用collection标签。使用type_id字段执行子查询getBooksByTypeId,将结果映射到books属性--><collection property="books" column="type_id" select="getBooksByTypeId"></collection> </resultMap> <!--3.子查询,根据类型编号查询所有对应的图书集合--> <select id="getBooksByTypeId" resultType="com.hqyj.ssm02.entity.BookInfo">select *from book_infowhere type_id = #{typeId} </select>
总结
-
多对一
如果查询要以从表数据为主,关联主表相应的数据时,属于多对一查询。如查询所有图书的同时,显示其类型。
建议使用子查询或自定义特殊的sql语句。都需要在从表实体中添加一个主表对象属性。
-
主表字段比较少,建议使用自定义特殊的sql语句,保证字段重命名为"主表对象.属性"
-
主表字段比较多,建议使用子查询,使用
<association>
标签映射主表对象属性
-
-
一对多
如果查询要以主表数据为主,关联该主键对应的从表实体对象的集合时,属于一对多查询。如查询某个类型,同时显示该类型下的所有图书。
建议使用子查询。在主表实体中添加一个从表集合属性。
使用
<collection>
标签映射从表集合属性
多条件查询
参考#{}的用法
#{}和${}
在mybatis的sql映射文件中,可以使用#{}和${}表示sql语句中的参数。
#{}相当于预处理,会将参数用?占位后传值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WKsexlaL-1676548026836)(D:/qqFileRecv/框架/框架/SSM04.assets/image-20230201174100891.png)]
${}相当于拼接,会将参数原样拼接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ybcWixzA-1676548026836)(D:/qqFileRecv/框架/框架/SSM04.assets/image-20230201174131695.png)]
通常使用#{},防止sql注入
-- #{}会自动用''将参数引起来,如参数为admin
select * from user where username=#{username}
select * from user where username='admin'
-- ${}会将参数原样拼接,如参数为admin
select * from user where username=${username}
select * from user where username=admin
#{}的使用
-
如果dao层接口中的方法参数为一个原始类型或字符串时
- sql语句#{}中的参数是接口中方法的形参名
public interface UserDao{User findById(int userId); }
<select id="findById" resultType="com.xxx.entity.User">select * from user where id=#{userId} </select>
-
如果dao层接口中的方法参数为一个实体对象时
- sql语句#{}中的参数必须是接口中方法参数的某个属性
public interface UserDao{User login(User user); }
<select id="login" resultType="com.xxx.entity.User">select * from user where username=#{username} and password=#{password} </select>
-
如果dao层接口中的方法参数不止一个时
-
给每个参数添加@Param(“自定义参数名”)
-
sql语句#{}中的参数必须是@Param注解中自定义的名称
public interface UserDao{User update(@Param("bianhao")int id,@Param("mima")String password); }
<update id="update">update user set password=#{mima} where id=#{bianhao} </update>
-
${}的使用
当需要拼接的参数不能带引号时,必须使用${},如动态表名、排序条件等
动态表名
select * from ${表名}
排序条件
select * from 表名 order by ${字段}
删除<delete></delete>
删除单个
delete from 表 where 字段 = 值
只需一个值即可,通常为主键id
dao层接口
int delete(int id);
sql映射文件
<delete id="delete">delete from book_info where book_id=#{id}
</delete>
删除多个
delete from 表 where 字段 in(数据集合)
如delete from book_info where book_id in (1001,1002,1005)
in后面的内容通常是一个数组
前端页面通过复选框选中要删除的数据,获取选中的数据的id,保存到一个数组中
dao层接口
int deleteByIds(@Param("idList")List<Integer> idList)
sql映射文件
<delete id="deleteByIds">delete from book_info where book_id in <!-- foreach标签用户遍历dao层传递的集合对象collection表示要遍历的集合open和close表示将遍历出的数据使用什么开头和结尾separator表示将遍历出的数据用什么分隔--><foreach collection="idList" item="id" separator="," open="(" close=")">#{id}</foreach>
</delete>
foreach标签可以用于sql语句中条件是集合的情况,配合where条件使用,dao层接口中方法参数为集合。
如where 字段 in/not in (值1,值2,值3)
添加<insert></insert>
添加时的参数虽多,但通常为一个完整的实体对象,所以dao层接口中方法的参数要定义成一个实体对象。
dao层
int insert(BookType bookType);
sql映射文件
- 通常加上parameterType参数指定添加的对象实体类全限定名
- #{}中的参数一定来自于dao层接口方法实体参数对象的属性
- 如果要在添加成功的同时,获取自动增长的主键值时,要添加
- useGeneratedKeys=“true” 表示自动获取自增的值
- keyColumn=“type_id” 表示自增字段
- keyProperty=“typeId” 表示自增字段对应的属性名
<insert id="testInsert" useGeneratedKeys="true" keyColumn="type_id" keyProperty="typeId"parameterType="com.hqyj.ssm02.entity.BookType">insert into book_typevalues (null, #{typeName})
</insert>
修改<update></update>
修改可以分为修改所有字段和修改部分字段
修改所有字段
dao层
int update(BookInfo bookInfo);
sql映射文件
<update id="update">update book_info setbook_name = #{bookName},book_author = #{bookAuthor},book_num = #{bookNum},book_price = #{bookPrice},publisher_date = #{publisherDate}where book_id=#{bookId}
</update>
这种方式会修改所有字段,如果实体参数的某个属性没有赋值,就会用该属性的默认值进行修改。
如String类型的属性没有赋值,就会用null修改,int类型用0修改,如果表中该字段非空,就会导致sql执行异常。
所以修改所有字段,参数为一个完整对象时,保证其中的属性都有值。
修改部分字段
方式一:dao层只写要修改的字段
dao
int updateSth(int bookId,int bookPrice,int bookNum);
sql映射文件
<update id="updateSth">update book_info setbook_num = #{bookNum},book_price = #{bookPrice}where book_id = #{bookId}
</update>
方式二:dao层写完整对象,sql语句中判断字段是否有值
dao
int updateSth(BookInfo bookInfo);
sql映射文件
<update id="updateSth">update book_info set<if test="bookPrice!=0">book_price = #{bookPrice},</if><if test="bookName!=null">book_name=#{bookName}</if>where book_id = #{bookId}
</update>
这样写,有以下几个问题
1.如果只有第一个条件满足,后续条件都不满足,最终拼接的sql语句,会在where关键字之前多出一个逗号,导致语法错误
解决方式:将set替换成<set>
标签,mybatis会自动去除最后的逗号
2.替换为<set>
标签后,如果所有条件都不满足,mybatis会自动去除set部分,sql语句就会变为update book_info where book_id=?,导致语法错误
解决方式:在<set>
标签中,添加一个不影响原始数据的条件,如 book_id = #{bookId},只需要一个book_id参数,sql语句没有语法错误,对原始数据没有任何影响
动态SQL
-
<set>
搭配<if>
用于修改<!--动态SQL:set-if用于修改--> <update id="testUpdate">update book_info<set>book_id=#{bookId},<if test="bookName!=null">book_name = #{bookName},</if><if test="bookPrice!=null">book_price = #{bookPrice}</if></set>where book_id=#{bookId} </update>
-
<where>
搭配<if>
用于查询、修改、删除时的条件<select id="queryByCondition" resultType="com.xxx.entity.BookInfo">select * from book_info<where><if test="bookName!=null">book_name = #{bookName}</if><if test="bookAuthor!=null">and book_author = #{bookAuthor}</if><if test="typeId!=null">and type_id = #{typeId}</if></where> </select>
-
<trim>
搭配<if>
可以替换set-if和where-if该标签有四个属性
prefix 表示如果trim标签中有if条件满足,就在整个trim部分添加指定前缀
suffix 表示如果trim标签中有if条件满足,就在整个trim部分添加指定后缀
prefixOverrides 表示去除整个trim部分多余的前缀
suffixOverrides 表示去除整个trim部分多余的后缀
使用trim实现修改
可以修改所有字段,也可以修改部分字段
<update id="testUpdate">update book_info<!--prefix="set"表示在所有内容前加入set关键字--><!--suffixOverrides=","表示所有内容之后如果有多余的逗号,去掉逗号--><trim prefix="set" suffixOverrides=",">book_id=#{bookId},<if test="bookName!=null">book_name = #{bookName},</if><if test="bookAuthor!=null">book_author = #{bookAuthor},</if><if test="bookNum!=null">book_num = #{bookNum},</if><if test="bookPrice!=null">book_price = #{bookPrice},</if><if test="publisherDate!=null">publisher_date = #{publisherDate}</if></trim>where book_id=#{bookId}
</update>
使用trim标签实现多条件查询
<select id="queryByCondition" resultType="com.hqyj.ssm02.entity.BookInfo">SELECT bi.*,type_name as 'bookType.typeName' FROM book_info bi,book_type bt<!--prefix="where"表示在所有内容前加入where关键字--><!--prefixOverrides="and"表示所有内容之前如果有多余的and,去掉and--><!--suffix="order by book_id desc"表示在所有内容之后加入order by book_id desc--><trim prefix="where" prefixOverrides="and" suffix="order by book_id desc">bi.type_id=bt.type_id<if test="bookName!=null">and book_name like concat ('%',#{bookName},'%')</if><if test="bookAuthor!=null">and book_author like concat ('%',#{bookAuthor},'%')</if><if test="typeId!=0">and bt.type_id =#{typeId}</if></trim>
</select>
多选删除具体实现
页面核心js
$(function () {//一键全选按钮$("#checkAll").click(function () {var state = this.checked;$(".checkDel").each(function () {this.checked = state;});});//删除所选按钮$("#deleteAll").click(function () {//定义保存id的数组var ids = [];//获取当前被选中的复选框所在的trlet $tr = $(".checkDel:checked").parent().parent();//遍历所选的tr$tr.each(function () {//获取id对应的td的值var id = $(this).children("td:eq(1)").text();//保存到数组中ids.push(id);});//至少选中一项if (ids.length == 0) {alert("请至少选中一项");return;}if (!confirm("确认要删除这" + ids.length + "条数据吗")) {return;}//使用ajax访问controller删除所选$.ajax({url: "${pageContext.request.contextPath}/bookInfo/deleteByChecked",data: {"ids": ids},type: "post",//ajax提交数组,需要添加一个参数traditional: true,success: function () {location.reload();}});});
});
dao
//批量删除
int deleteByIds(@Param("idList") List<Integer> idList);
mapper.xml
<!--批量删除-->
<delete id="deleteByIds">delete from book_info where book_id in<foreach collection="idList" item="id" open="(" close=")" separator=",">#{id}</foreach>
</delete>
service
public void deleteByChecked(Integer[] ids) {//将数组转换为集合List<Integer> list = Arrays.asList(ids);bookInfoDao.deleteByIds(list);
}
controller
@RequestMapping("/deleteByChecked")
public String deleteByChecked(Integer[] ids){bookInfoService.deleteByChecked(ids);return "redirect:queryAll";
}
多条件查询具体实现
搜索表单
<form class="navbar-form navbar-left"action="${pageContext.request.contextPath}/bookInfo/queryByCondition"><div class="form-group"><input type="text" class="form-control" name="bookName" placeholder="请输入书名关键字"><input type="text" class="form-control" name="bookAuthor" placeholder="请输入作者关键字"><select id="topSelect" class="form-control" name="typeId"><option value="0">全部</option></select></div><button type="submit" class="btn btn-default">搜索</button>
</form>
dao
List<BookInfo> queryByCondition(BookInfo bookInfo);
mapper.xml
<select id="queryByCondition" resultType="com.hqyj.ssm02.entity.BookInfo">SELECT bi.*,type_name as 'bookType.typeName' FROM book_info bi,book_type bt<trim prefix="where" prefixOverrides="and" suffix="order by book_id desc">bi.type_id=bt.type_id<if test="bookName!=null">and book_name like concat ('%',#{bookName},'%')</if><if test="bookAuthor!=null">and book_author like concat ('%',#{bookAuthor},'%')</if><if test="typeId!=0">and bt.type_id =#{typeId}</if></trim>
</select>
service
public List<BookInfo> queryByCondition(BookInfo bookInfo) {return bookInfoDao.queryByCondition(bookInfo);
}
controller
@RequestMapping("/queryByCondition")
public String queryByCondition(BookInfo bookInfo,Model model) {List<BookInfo> list= bookInfoService.queryByCondition(bookInfo);model.addAttribute("list",list);return "bookInfoList";
}
分页
使用分页组件PageHelper
1.导入依赖
<!--分页组件-->
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.3.2</version>
</dependency>
2.在mybatis的配置文件中
<!--设置分页插件-->
<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"><!--保证翻页不会超出范围--><property name="reasonable" value="true"/></plugin>
</plugins>
3.使用
- 通过PageHelper类调用静态方法startPage(当前页数,每页显示的记录数)开启分页
- 查询所有,返回集合
- 创建PageInfo分页模型对象,构造方法的参数为查询出的集合,设置泛型,
//定义当前页和每页显示的数量
int pageNum=1;
int size=10;
//开启分页
PageHelper.startPage(pageNum,size);
//正常调用查询,返回查询到的数据集合
BookInfo bookInfo = new BookInfo();
bookInfo.setTypeId(1);
List<BookInfo> list = bookInfoDao.queryByCondition(bookInfo);//创建分页模型对象,构造方法的参数为查询出的结果,设置泛型,
PageInfo<BookInfo> pageInfo = new PageInfo<>(list);//此时分页相关数据都保存在pageInfo对象中
System.out.println("总记录数"+pageInfo.getTotal());
System.out.println("最大页数"+pageInfo.getPages());
System.out.println("当前页"+pageInfo.getPageNum());
System.out.println("当前容量"+pageInfo.getSize());
System.out.println("当前分页数据"+pageInfo.getList());
System.out.println("是否有上一页"+pageInfo.isHasPreviousPage());
System.out.println("是否有下一页"+pageInfo.isHasNextPage();
System.out.println("是否是首页"+pageInfo.isIsFirstPage());
System.out.println("是否是尾页"+pageInfo.isIsLastPage());
PageInfo对象常用属性和方法 | 作用 |
---|---|
total/getTotal() | 得到总记录数 |
pages/getPages() | 得到最大页数 |
pageNum/getPageNum() | 得到当前页 |
size/getSize() | 得到每页显示的记录数 |
list/getList() | 得到按当前page和size查询到的数据集合 |
isFirstPage/isIsFirstPage() | 是否是首页 |
isLastPage/isIsLastPage() | 是否是尾页 |
多条件分页具体实现
controller
@RequestMapping("/queryByCondition")
public String queryByCondition(@RequestParam(defaultValue = "1") int pageNum,@RequestParam(defaultValue = "8") int size,BookInfo bookInfo,Model model) {//1.PageHelper.startPage()PageHelper.startPage(pageNum, size);//2.查询集合List<BookInfo> list = bookInfoService.queryByCondition(bookInfo);//3.PageInfo(集合)PageInfo<BookInfo> pageInfo = new PageInfo<>(list);//将查询到的分页模型对象保存到model中model.addAttribute("pageInfo",pageInfo);//构造页数的集合ArrayList<Integer> pageList = new ArrayList<>();for (int i = 1; i <= pageInfo.getPages(); i++) {pageList.add(i);}model.addAttribute("pageList",pageList);return "bookInfoList";
}
页面分页组件
<nav aria-label="Page navigation"><ul class="pagination"><c:if test="${!pageInfo.isFirstPage}"><li><a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pageInfo.pageNum-1}"aria-label="Previous"><span aria-hidden="true">«</span></a></li></c:if><c:forEach items="${pageList}" var="pno"><li class="pno"><a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pno}">${pno}</a></li></c:forEach><c:if test="${!pageInfo.isLastPage}"><li><a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pageInfo.pageNum+1}"aria-label="Next"><span aria-hidden="true">»</span></a></li></c:if></ul>
</nav>
SpringBoot
Spring推出的一个Spring框架的脚手架。
不是一个新的框架,而是搭建Spring相关内容框架的平台。
它省去了Spring、SpringMVC项目繁琐的配置过程,让开发变得更加简单。
本质还是Spring+SpringMVC,可以搭配其他的ORM框架,如MyBatis、MyBatisPlus、JPA、Hibernate等。
特点
- 内置了Tomcat服务器,不需要部署项目到Tomcat中
- 内置了数据源Hikari
- 减少了jar文件依赖的配置
- SpringBoot中的配置文件可以使用yml格式文件,代替properties或xml
创建SpringBoot项目
通过IDEA创建
通过官网模板创建
官网模板https://start.spring.io/
点击生成,会下载一个压缩文件,解压后通过IDEA打开。
无论哪种方式,都需要重写设置Maven配置文件
创建成功后的目录结构
第一个springboot项目的helloworld
热部署
项目在开发过程中,可以不需要每次都重启,等待一段时间后会自动更新编译运行
使用
添加依赖,可以在创建的项目的时候选择,也可以中途添加
<!--热部署-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><version>2.7.8</version>
</dependency>
开启热部署
Lombok
用于简化实体类中模板代码的工具
使用
添加依赖,可以在创建的项目的时候选择,也可以中途添加
<!--Lombok-->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><scope>provided</scope>
</dependency>
安装插件(IDEA2020.2之后的版本会内置Lombok插件,无需安装)
-
IDEA插件官网Versions: Lombok – IntelliJ IDEs Plugin | Marketplace (jetbrains.com)
-
IDEA内置插件市场搜索
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xEZQPWWk-1676548026840)(D:/qqFileRecv/框架/框架/day10-SpringBoot+MyBatis1.assets/image-20230203140602578.png)]
在某个实体类上添加注解
lombok常用注解 | 作用 |
---|---|
@AllArgsConstructor | 自动生成全参构造方法 |
@Data | 以下注解之和 |
@Setter | 自动生成set方法 |
@Getter | 自动生成get方法 |
@NoArgsConstructor | 自动生成无参构造方法 |
@ToString | 自动生成toString方法 |
@EqualsAndHashcode | 自动生成equals和hashcode方法 |
SpringBoot+MyBatis实现单表查询
1.创建好SpringBoot项目
最好在创建的时候选择以下依赖
- spring-web(必选)
- lombok
- spring-devtools
- springboot集成mybatis
- mysql驱动
都可以后续添加
<!--web-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!--热部署-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><version>2.7.8</version>
</dependency><!--Lombok-->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><scope>provided</scope>
</dependency>
2.在pom.xml中添加mybatis集成SpringBoot依赖和数据库驱动
<!--mysql-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.31</version>
</dependency>
<!--springboot集成MyBatis-->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version>
</dependency>
3.在springboot配置文件application.properties中
#注释
#.properties文件称为属性文件,数据以键值对"键=值"的形式保存
#设置项目启动端口号
#server.port=9090
#设置项目上下文路径
#server.servlet.context-path=/springbootday1#mybatis相关配置
#开启驼峰命名映射
mybatis.configuration.map-underscore-to-camel-case=true
#打印sql语句
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#扫描mybatis的sql映射文件(将mapper.xml文件保存在resources目录下的mapper目录下)
mybatis.mapper-locations=classpath:mapper/*.xml#数据库信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
mybatis的sql映射文件模板
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--设置该文件对应的dao层接口全限定名-->
<mapper namespace=""></mapper>
4.根据数据表创建实体类、dao层接口、service、controller
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1SwbwcE-1676548026840)(D:/qqFileRecv/框架/框架/day10-SpringBoot+MyBatis1.assets/image-20230203154807705.png)]
Hero
@Data
public class Hero{private Integer id;private String name;private String position;private String sex;private Double price;private String shelfDate;
}
dao
@Repository
public interface HeroDao{List<Hero> queryAll();
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--设置该文件对应的dao层接口全限定名-->
<mapper namespace="com.xxx.dao.HeroDao"><select id="queryAll" resultType="com.xxx.entity.Hero">select * from hero</select>
</mapper>
service
@Service
public class HeroService{@Autowiredprivate HeroDao heroDao;public List<Hero> queryAll(){return heroDao.queryAll(); }
}
controller
@Controller
@RequestMapping("/hero")
public class HeroController{@Autowiredprivate HeroService heroService;@RequestMapping("/queryAll")@ResponseBodypublic List<Hero> queryAll(){return heroDao.queryAll(); }
}
5.在SpringBoot的启动类上,加入@MapperScan注解,扫描dao层所在根包
@SpringBootApplication
//扫描dao层所在的根包
@MapperScan("com.hqyj.first.dao")
public class FirstApplication {public static void main(String[] args) {SpringApplication.run(FirstApplication.class, args);}}
启动项目,按自定的项目上下文路径和端口号访问某个controller中的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3V572UD0-1676548026841)(D:/qqFileRecv/框架/框架/day10-SpringBoot+MyBatis1.assets/image-20230203155852438.png)]
SpringBoot+LayUI实现酒店客房管理
核心知识点
-
SpringBoot项目搭建
-
核心依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency>
-
集成MyBatis
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version> </dependency>
-
-
新注解
-
@RestController
如果某个控制器类中的所有方法都要加@ResponseBody,可以在类上加@ResponseBody,也可以用@RestController代替@ResponseBody和@Controller
-
-
LayUI
- 数据表格
- 数据接口格式
- 真假分页
- 头工具栏事件
- 行内事件
- 单元格编辑事件
- 弹出层
- layer.msg()
- layer.confirm()
- layer.open()
- 数据表格
-
ajax
$.ajax({url:"",data:xxx,type:"get/post",success:function(){} })
-
前后端分离
- 该案例可以将页面独立出来,成为一个前后端分离项目,也可以将页面作为静态资源保存在static目录下
-
如果设计为前后端分离,要在控制器类上加入@CrossOrigin,表示该类中的所有方法允许跨域请求
-
打包SpringBoot项目
保证项目中无错误,包含单元测试中
打包后是一个.jar文件,位于target目录中
在安装有java环境的机器中,控制台运行jar文件
java -jar 文件名.jar
数据库部分设计
数据库表详细设计
核心Java代码
实体类
订单表
/*
* 订单
* */
@Data
public class Orders {private Integer id;private Integer roomNo;private Integer cusId;//@JsonFormat是springboot集成的jackson包中的注解,用于格式化日期//pattern指定格式日期 timezone指定时区,这里和数据库的时区保持一致@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")private Date leaveTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")private Date stayTime;private Integer cost;//显示订单的同时,要显示房间的相关信息private Room room;//显示订单的同时,要显示客户的相关信息private Customer customer;
}
数据访问层
-
实现随意修改某个字段的值
int update(@Param("field") String field,@Param("value") String value,@Param("id") int id);
sql映射文件
<update id="update">update room<set><if test="field=='roomType'">room_type= #{value}</if><if test="field=='roomPrice'">room_price= #{value}</if><if test="field=='roomUse'">room_use=#{value}</if></set>where room_no=#{id} </update>
-
添加时获取自增的值
int insert(Customer customer);
sql映射文件
<!--添加顾客的同时,获取自增的id--> <insert id="insert" useGeneratedKeys="true" keyColumn="cus_id" keyProperty="cusId">insert into customer values(null,#{cusName},#{cusPhone},#{cusIdcard}) </insert>
-
多表连接查询/条件查询
List<Orders> queryAll();List<Orders> search(String keyword);//根据客户编号查询是否已入住 Orders isStay(Integer cusId);//根据房间号查询正在入住的订单信息 Orders findCheckInByRoomNo(Integer roomNo);//计算费用 int checkOut(@Param("roomNo") Integer roomNo,@Param("cost") Integer cost);
sql映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--设置该文件对应的dao层接口全限定名--> <mapper namespace="com.hqyj.hotel_sys.dao.OrdersDao"><insert id="insert">insert into ordersvalues (null, #{roomNo}, #{cusId}, now(), null, 0)</insert><!--关联查询方式一:特殊的SQL语句--><!--<select id="queryAll" resultType="com.hqyj.hotel_sys.entity.Orders">select o.*,cus_name as 'customer.cusName',cus_phone as 'customer.cusPhone',cus_idcard as 'customer.cusIdcard',room_type as 'room.roomType',room_price as 'room.roomPrice',room_use as 'room.roomUse'from customer c,room r,orders owhere c.cus_id = o.cus_idand r.room_no = o.room_no</select>--><!--多表连接条件查询--><select id="search" resultMap="ordersMap">SELECT * FROM orders o,customer c<trim prefix="where" prefixOverrides="and">o.cus_id = c.cus_id<if test="keyword!=null">and cus_name like concat('%',#{keyword},'%')</if></trim></select><!--关联查询方式二:子查询--><!--1.查询自身表--><select id="queryAll" resultMap="ordersMap">select *from orders</select><!--自定义结果集映射,用cus_id和room_no做子查询,重新映射一次到orders对象--><resultMap id="ordersMap" type="com.hqyj.hotel_sys.entity.Orders"><result property="roomNo" column="room_no"></result><result property="cusId" column="cus_id"></result><association property="room" column="room_no" select="findRoomByNo"></association><association property="customer" column="cus_id" select="findCustomerById"></association></resultMap><!--子查询一:根据房号查房间--><select id="findRoomByNo" resultType="com.hqyj.hotel_sys.entity.Room">select *from roomwhere room_no = #{roomNo}</select><!--子查询二:根据编号查客户--><select id="findCustomerById" resultType="com.hqyj.hotel_sys.entity.Customer">select *from customerwhere cus_id = #{cusId}</select><select id="isStay" resultType="com.hqyj.hotel_sys.entity.Orders">select *from orderswhere cus_id = #{cusId}and leave_time is null</select><!--根据房号查询正在入住的订单信息,包含房间价格--><select id="findCheckInByRoomNo" resultType="com.hqyj.hotel_sys.entity.Orders">select o.*, room_price as 'room.roomPrice'from orders o,room rwhere o.room_no = r.room_noand r.room_no = #{roomNo}and leave_time is null</select><update id="checkOut">update ordersset leave_time=now(),cost=#{cost}where room_no = #{roomNo}and leave_time is null</update> </mapper>
业务流程层
package com.hqyj.hotel_sys.service;import com.hqyj.hotel_sys.dao.CustomerDao;
import com.hqyj.hotel_sys.dao.OrdersDao;
import com.hqyj.hotel_sys.dao.RoomDao;
import com.hqyj.hotel_sys.entity.Customer;
import com.hqyj.hotel_sys.entity.Orders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.Date;
import java.util.List;@Service
public class CustomerService {@Autowiredprivate CustomerDao customerDao;@Autowiredprivate RoomDao roomDao;@Autowiredprivate OrdersDao ordersDao;/** 入住* 1.获取顾客信息:姓名、电话、身份证号,* 根据身份证号判断是否存在,如果存在,用存在的对象* 如果不存在,向customer表中添加一条记录 insert into customer values(null,#{cusName},#{cusPhone},#{cusIdcard})* 添加成功时,要获取自增的id,用于订单表中使用* 判断该顾客是否已入住:查询订单表中对应顾客编号的记录,如果退房时间为空,说明已入住* select * from orders where cus_id=#{cusId} and leave_time is null* 2.将对应房间的状态改为1,对room表中修改 update room set room_use=1 where room_no=#{roomNo}* 3.向订单表中添加一条记录 insert into orders values (null,#{roomNo},#{cusId},now(),null,0)** 以上3个步骤属于一个事务,要在该方法上加事务注解* */@Transactional//让该方法成为一个事务,如果执行中途出错,会自动回滚public boolean checkIn(Customer customer, int roomNo) {//1.添加客户,根据身份证号判断是否存在Customer byIdcard = customerDao.findByIdcard(customer.getCusIdcard());boolean b1;if (byIdcard == null) {//不存在,调用添加b1 = customerDao.insert(customer) > 0;} else {customer = byIdcard;b1 = true;}//查看是否已入住if (ordersDao.isStay(customer.getCusId()) != null) {return false;}//2.修改房间状态boolean b2 = roomDao.update("roomUse", "1", roomNo) > 0;//3.添加订单信息//创建订单对象Orders orders = new Orders();//客户编号在添加客户成功后,会自动获取orders.setCusId(customer.getCusId());orders.setRoomNo(roomNo);boolean b3 = ordersDao.insert(orders) > 0;return b1 & b2 & b3;}/** 退房* 1.根据房间号查询对应正在入住的订单记录(同时查询出房价)* select o.* from orders o,room r where o.room_no=r.room_no and r.room_no =#{roomNo} and leave_time is null* 2.结算* 添加退房时间、添加花费* update orders set leave_time =now() ,cost=#{cost} where room_no =#{roomNo} and leave_time is null* 3.修改房间状态为空闲* update room set room_use = 0 where room_no=#{roomNo}* */@Transactionalpublic boolean checkOut(Integer roomNo) {//1.根据房间号查询对应正在入住的订单记录Orders orders = ordersDao.findCheckInByRoomNo(roomNo);//2.计算费用Date stayTime = orders.getStayTime();Date now = new Date();double l = now.getTime() - stayTime.getTime();//转换为天数double day = Math.ceil(l / 1000 / 3600 / 24);double cost = orders.getRoom().getRoomPrice() * day;//修改订单中的花费和退房时间//update orders set leave_time=now(),cost=#{cost} where room_no=#{roomNo} and leave_time is nullboolean b1 = ordersDao.checkOut(roomNo, (int) cost) > 0;//3.修改房间状态为空闲0//update room set room_use where room_no = #{roomNo}boolean b2 = roomDao.update("roomUse", "0", roomNo) > 0;return b1 & b2;}
}
控制层
如果某个控制器中的所有方法都需要返回JSON格式字符串,在类上加@ResponseBody
@Controller
@RequestMapping("/room")
public class RoomController {@Autowiredprivate RoomService roomService;@ResponseBody@RequestMapping("/queryAll")public ResultData<Room> queryAll(Integer page, Integer limit) {//使用PageHelper分页PageHelper.startPage(page, limit);//正常查询所有List<Room> rooms = roomService.queryAll();//创建分页模型对象PageInfo<Room> pageInfo = new PageInfo<>(rooms);//返回的数据为分页后的集合,数量为总记录数return new ResultData<>((int)pageInfo.getTotal(),pageInfo.getList());}
}
LayUI数据表格
数据表格所需的数据接口格式为
{code:0,msg:"",count:1000,data:[{},{}]
}
构造满足LayUI数据表格的数据模型类ResultData
/*
* 定义一个用于LayUI数据表格对应格式的模板类
* code 状态码 0成功
* msg 提示文字
* count 数据总量
* data 数据集合
* */
@Data
public class ResultData<T> {private Integer code;private String msg;private Integer count;private List<T> data;/** 定义带count和data的构造方法,用于初始化code和msg* */public ResultData(Integer count, List<T> data) {code=0;msg="";this.count = count;this.data = data;}
}
最后在控制层中,将查询的方法的返回值更改为ResultData类型
@RequestMapping("/queryAll")
public ResultData<Room> queryAll() {List<Room> rooms = roomService.queryAll();return new ResultData<>(rooms.size(), rooms);
}
最终页面
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>LayUI</title><meta name="renderer" content="webkit"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><link rel="stylesheet" href="../LayUI/css/LayUI.css" media="all"><!-- 注意:如果你直接复制所有代码到本地,上述css路径需要改成你本地的 -->
</head>
<body>
<table class="LayUI-hide" id="test" lay-filter="test"></table>
<script type="text/html" id="toolbarDemo"><div class="LayUI-btn-container"><button class="LayUI-btn LayUI-btn-sm" lay-event="getCheckData">获取选中行数据</button><button class="LayUI-btn LayUI-btn-sm" lay-event="getCheckLength">获取选中数目</button><button class="LayUI-btn LayUI-btn-sm" lay-event="isAll">验证是否全选</button><button class="LayUI-btn LayUI-btn-sm" lay-event="addRoom">添加客房</button></div>
</script>
<script type="text/html" id="barDemo"><!--插值表达式-->{{# if(d.roomUse==0){ }}<a class="LayUI-btn LayUI-btn-normal LayUI-btn-sm" lay-event="check-in">入住</a><a class="LayUI-btn LayUI-btn-danger LayUI-btn-sm" lay-event="del">删除</a>{{# }else{ }}<a class="LayUI-btn LayUI-btn-warm LayUI-btn-sm" lay-event="check-out">退房</a>{{# } }}
</script><script src="../LayUI/LayUI.js" charset="utf-8"></script>
<!-- 注意:如果你直接复制所有代码到本地,上述 JS 路径需要改成你本地的 -->
<script>LayUI.use('table', function () {var table = LayUI.table;//引入Jqueryvar $ = LayUI.$;/** 渲染表格数据* url:数据访问接口* cols:表格列* field:数据字段名* title:表头* cellminwidth:100 自适应宽度* fixed:left 固定在某侧* unresize:true 不可改变尺寸* sort:true 排序* edit:true 行内编辑* */table.render({elem: '#test'//设置数据接口, url: 'http://localhost:9090/hotel/room/queryAll', toolbar: '#toolbarDemo' //开启头部工具栏,并为其绑定左侧模板, defaultToolbar: ['filter', 'exports', 'print', { //自定义头部工具栏右侧图标。如无需自定义,去除该参数即可title: '提示', layEvent: 'LAYTABLE_TIPS', icon: 'LayUI-icon-tips'}], title: '用户数据表', cols: [[{type: 'checkbox', fixed: 'left'}, {field: 'roomNo', title: '房间号', cellminwidth: 100, fixed: 'left', unresize: true, sort: true}, {field: 'roomType', title: '房间类型', cellminwidth: 100, edit: 'text'}, {field: 'roomPrice', title: '房间单价', cellminwidth: 100, edit: 'text', sort: true}, {field: 'roomUse', title: '使用状态', cellminwidth: 100,templet: function (res) {return res.roomUse == 0 ? "<span style='color:green'>空闲中</span>" : "<span style='color:red'>使用中</span>";}}, {fixed: 'right', title: '操作', toolbar: '#barDemo', cellminwidth: 100,}]]//开启分页组件, page: true//设置每页显示记录数的下拉选项, limits: [5, 10, 20]//默认每页显示的条数,没有设置默认为10, limit: 10//解析数据表格url请求后的数据, parseData: function (res) {//res就是url对应的数据/*//假分页var result;if (this.page.curr) {result = res.data.slice(this.limit * (this.page.curr - 1), this.limit * this.page.curr);} else {result = res.data.slice(0, this.limit);}*/return {"code": res.code,"msg": res.msg,"count": res.count,"data": res.data}}});//头工具栏事件table.on('toolbar(test)', function (obj) {var checkStatus = table.checkStatus(obj.config.id);switch (obj.event) {case 'addRoom':layer.open({title: '添加客房',type: 2,content: 'addRoom.html',area: ['350px', '250px'],resize: false,anim: 2,/* success: function(layero, index){console.log(layero, index);}*/})break;case 'getCheckData'://data是所选数据的集合var data = checkStatus.data;for (var i = 0; i < data.length; i++) {console.log(data[i].roomNo)}// layer.alert(JSON.stringify(data));break;case 'getCheckLength':var data = checkStatus.data;layer.msg('选中了:' + data.length + ' 个');break;case 'isAll':layer.msg(checkStatus.isAll ? '全选' : '未全选');break;//自定义头工具栏右侧图标 - 提示case 'LAYTABLE_TIPS':layer.alert('这是工具栏右侧自定义的一个图标按钮');break;};});//监听单元格编辑事件table.on('edit(test)', function (obj) {// console.log(obj);//layer.confirm("提示文件",function(){确认触发},function(){取消触发})layer.confirm('确认要修改吗', function (index) {//使用ajax提交要修改的字段名、修改后的值、要修改的编号$.ajax({url: "http://localhost:9090/hotel/room/update",data: {"field": obj.field,//要修改的字段"value": obj.value,//修改后的值"id": obj.data.roomNo//要修改的id},success: function () {//修改成功,关闭确认框layer.close(index);}});}, function () {//修改失败,重新加载location.reload()})})//监听行工具事件table.on('tool(test)', function (obj) {//data就是当前行中的数据var data = obj.data;// console.log(obj)if (obj.event === 'del') {//弹出确认框layer.confirm('真的删除行么', function (index) {//使用ajax提交要删除的id$.ajax({url: "http://localhost:9090/hotel/room/delete",data: {"delId": data.roomNo},success: function (res) {if (res) {//前端假删除obj.del();//关闭确认框layer.close(index);}}});});} else if (obj.event === 'check-in') {//弹出输入顾客信息表单layer.open({title: '输入顾客信息',type: 2,content: 'addCustomer.html',area: ['350px', '400px'],resize: false,anim: 2,//弹出窗口后的回调函数success: function (layero, index) {//弹出成功后,在弹出页面中加入当前点击行的roomNo//获取弹出层的body部分var body = layer.getChildFrame('body', index);//获取弹出层body中的隐藏域,给其赋值body.find("input[name=roomNo]").val(data.roomNo);}})} else if (obj.event === 'check-out') {layer.confirm("确定要退房吗", function () {$.ajax({url: "http://localhost:9090/hotel/customer/checkOut",data: {"roomNo": data.roomNo},success: function (res) {if (res) {location.reload();}}});})}});});
</script></body>
</html>
LayUI添加页面
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="../LayUI/css/LayUI.css" media="all"></head><body><form class="LayUI-form" action=""><div class="LayUI-form-item"><label class="LayUI-form-label">房间类型</label><div class="LayUI-input-inline"><input type="text" name="roomType" required lay-verify="required" placeholder="请输入房间类型" autocomplete="off" class="LayUI-input"></div></div><div class="LayUI-form-item"><label class="LayUI-form-label">房间单价</label><div class="LayUI-input-inline"><input type="text" name="roomPrice" required lay-verify="required" placeholder="请输入房间单价" autocomplete="off" class="LayUI-input"></div></div><div class="LayUI-form-item"><div class="LayUI-input-block"><button class="LayUI-btn" lay-submit lay-filter="formDemo">立即提交</button><button type="reset" class="LayUI-btn LayUI-btn-primary">重置</button></div></div></form><script src="../LayUI/LayUI.js" charset="utf-8"></script><script>//DemoLayUI.use('form', function(){var form = LayUI.form;//使用LayUI内置jqueryvar $=LayUI.$;//监听提交form.on('submit(formDemo)', function(data){// data.field是当前表单中的所有数据// layer.msg(JSON.stringify(data.field));$.ajax({url:'http://localhost:9090/hotel/room/addRoom',//将表单中的所有数据一起提交,实际提交的是name=value,如roomType=值&roomPrice=值data:data.field,//相当于/* data:{roomType:"",roomPrice:""}*/success:function(res){if(res){//刷新父页面parent.location.reload()}}})//return false时不提交表单return false;});});</script></body>
</html>
LayUI分页
-
假分页
查询所有,在页面中分页,适合记录比较少的情况
table.render({//省略url等//开启分页组件, page: true//设置每页显示记录数的下拉选项, limits: [5, 10, 20]//默认每页显示的条数,没有设置默认为10, limit: 10//解析数据表格url请求后的数据, parseData: function (res) {//res就是url对应的数据//假分页var result;if (this.page.curr) {result = res.data.slice(this.limit * (this.page.curr - 1), this.limit * this.page.curr);} else {result = res.data.slice(0, this.limit);}return {"code": res.code,"msg": res.msg,"count": res.count,"data": result}} })
-
真分页
可以使用PageHelper组件
依赖
<!--分页组件SpringBoot集成PageHelper--> <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.6</version> </dependency>
在application.properties中配置
# 防止不合理分页 pagehelper.reasonable=true
在控制层中使用
@ResponseBody @RequestMapping("/queryAll") //这里的page和limit参数是layui数据表格自动传递 public ResultData<Room> queryAll(Integer page, Integer limit) {//使用PageHelper分页PageHelper.startPage(page, limit);//正常查询所有List<Room> rooms = roomService.queryAll();//创建分页模型对象PageInfo<Room> pageInfo = new PageInfo<>(rooms);//返回的数据为分页后的集合,数量为总记录数return new ResultData<>((int)pageInfo.getTotal(),pageInfo.getList()); }
LayUI条件查询
页面头部工具栏中加入搜索框
<script type="text/html" id="toolbarDemo"><div class="layui-form-item"><div class="layui-input-inline"><input type="text" name="keyword" required lay-verify="required" placeholder="请输入姓名关键字" autocomplete="off"class="layui-input"></div><button class="layui-btn layui-btn-primary" lay-event="search">搜索</button></div>
</script>
js部分
//头工具栏事件
table.on('toolbar(test)', function (obj) {switch (obj.event) {case 'search'://获取输入的内容let keyword = $("input[name=keyword]").val();if(keyword==""){layer.msg("输入不能为空");return;}//如果要修改数据表格中的数据,只能更改url的地址后,重新加载数据表格table.reload('test',{url:"http://localhost:9090/hotel/orders/search?keyword="+keyword});break;}
});
dao层参考上方数据访问层条件查询部分
MyBatisPlus
官网简介 | MyBatis-Plus (baomidou.com)
MyBatis-Plus (简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
只需简单的配置,就能实现对单表的CURD。
其核心有两个接口:BaseMapper和IService
BaseMapper中封装了大量数据访问层的方法
IServcie中封装了大量业务流程层的方法
SpringBoot+MyBatisPlus
1.创建SpringBoot项目
创建时勾选依赖
- devtools
- lombok
- spring-web
- mysql-driver
2.导入SpringBoot集成MyBatisPlus依赖
<!-- SpringBoot集成MyBatisPlus -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version>
</dependency>
3.配置application.properties文件
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root# 开启sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 无需加入开启驼峰命名映射,MyBatisPlus默认使用驼峰命名进行属性-字段映射
#mybatis-plus.configuration.map-underscore-to-camel-case=true
4.根据数据表创建实体类
实体类的属性名命名方式:
- MyBatisPlus默认使用驼峰命名对字段和属性进行映射。如将字段stu_name对应的属性写为stuName
- 如果字段名和属性名不一致,在属性名上加入**@TableField(value = “字段名”)**
- 主键字段对应的属性,需要加入@TableId注解,其type属性表示主键生成策略
- @TableId(type = IdType.AUTO)表示主键自增,在数据库也要将主键设置为自增
- @TableId(type = IdType.ASSIGN_ID)//IdType.ASSIGN_ID表示使用"雪花算法"(根据时间和机器特征码)生成一个id
- @TableId(type = IdType.ASSIGN_UUID)//IdType.ASSIGN_UUID表示使用UUID生成一个随机字符串id
@Data
public class Hero {//type表示主键生成策略,@TableId(type = IdType.AUTO)// IdType.AUTO表示主键自增,在数据库也要将主键设置为自增//@TableId(type = IdType.ASSIGN_ID)//IdType.ASSIGN_ID表示使用"雪花算法"(根据时间和机器特征码)生成一个id//@TableId(type = IdType.ASSIGN_UUID)//IdType.ASSIGN_UUID表示使用UUID生成一个随机字符串idprivate Integer id;//如果属性名和字段名不一致@TableField(value = "name")private String heroName;private String position;private String sex;private Integer price;private String shelfDate;
}
5.编写数据访问层接口
可以不用写@Repository,继承BaseMapper接口,设置泛型
/*
* 数据访问层可以称为dao或mapper层
* 可以不用加@Repository注解
* */
public interface HeroMapper extends BaseMapper<Hero> {}
6.在SpringBoot的启动类中,扫描数据访问层所在包
@SpringBootApplication
@MapperScan("com.hqyj.sbmp01.mapper")
public class Sbmp01Application {public static void main(String[] args) {SpringApplication.run(Sbmp01Application.class, args);}
}
测试
在SpringBoot自带的单元测试类中,注入HeroMapper对象,调用BaseMapper中定义的方法即可实现CURD。
BaseMapper接口
BaseMapper接口中定义了常用的增删改查方法,
在数据访问层接口中继承该接口
方法列表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PJYyGHZA-1676548026845)(D:/qqFileRecv/框架/框架/SpringBoot+MyBatisPlus.assets/image-20230208172250774.png)]
使用
public interface HeroMapper extends BaseMapper<Hero> {}
BaseMapper接口中的常用方法
方法名 | 参数 | 作用 |
---|---|---|
selectList(Wrapper wrapper) | 条件构造器 | 根据条件查询集合,如果实参为null表示查询所有,返回List集合 |
selectById(Serializable id) | 主键 | 根据主键查询单个对象,返回单个对象 |
selectOne(Wrapper wrapper) | 条件构造器 | 条件查询单个对象,返回单个对象 |
insert(T entity) | 实体对象 | 添加单个实体 |
updateById(T entity) | 实体对象 | 根据实体对象单个修改,对象必须至少有一个属性和主键 |
update(T entity,Wrapper wrapper) | 实体对象和条件构造器 | 根据条件修改全部,对象必须至少有一个属性 |
deleteById(Serializable id/T entity) | 主键/实体对象 | 根据主键删除单个对象 |
deleteBatchIds(Collection ids) | 主键集合 | 根据集合删除 |
delete(Wrapper wrapper) | 条件构造器 | 根据条件删除,如果实参为null表示无条件删除所有 |
测试
package com.hqyj.sbmp01;import com.hqyj.sbmp01.entity.Hero;
import com.hqyj.sbmp01.mapper.HeroMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;@SpringBootTest
class Sbmp01ApplicationTests {@Autowired//@Resourceprivate HeroMapper mapper;@Testvoid contextLoads() {//查询所有List<Hero> list = mapper.selectList(null);for (Hero hero : list) {System.out.println(hero);}}@Testvoid test1() {//根据主键查询单个对象System.out.println(mapper.selectById(20));}@Testvoid test2() {//根据id删除,参数为id//System.out.println(mapper.deleteById(20));//根据id删除,参数为带有id的对象/*Hero hero = new Hero();hero.setId(100);System.out.println(mapper.deleteById(hero));*///删除所有//mapper.delete(null);//根据id集合删除ArrayList<Integer> list = new ArrayList<>();list.add(99);list.add(100);list.add(88);list.add(69);System.out.println(mapper.deleteBatchIds(list));}@Testvoid test3() {//根据id修改//参数为一个对象,这个对象至少要有一个非主键属性有值Hero hero = new Hero();hero.setHeroName("a");hero.setPosition("b");hero.setSex("c");hero.setShelfDate("d");hero.setPrice(123);hero.setId(999);//System.out.println(mapper.updateById(hero));// 修改全部数据// mapper.update(修改后的对象,条件构造器)mapper.update(hero,null);}@Testvoid test4() {Hero hero = new Hero();hero.setHeroName("asdfsdfsdf");hero.setPosition("b");hero.setSex("c");hero.setShelfDate("1999-9-9");hero.setPrice(123);mapper.insert(hero);}
}
IService接口
IService接口减少业务逻辑层的代码,并对BaseMapper进行了拓展
在业务流程类中继承该接口
部分方法列表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NRJfxDEr-1676548026845)(D:/qqFileRecv/框架/框架/SpringBoot+MyBatisPlus.assets/image-20230208172729669.png)]
使用
-
1.创建一个业务层接口,继承IService接口,设置泛型
/* * 创建业务逻辑层接口,继承IService<T>接口,设置泛型 * */ public interface HeroService extends IService<Hero> { }
-
2.创建一个业务层接口的实现类,添加@Service注解,继承 ServiceImpl<M, T>,实现上一步创建的接口
- M是数据访问层接口
- T是实体类
/* * 1.添加@Service * 2.继承ServiceImpl<M, T> M是Mapper类 T是实体类 * 3.实现自定义Service接口 * */ @Service public class ImpHeroService extends ServiceImpl<HeroMapper, Hero> implements HeroService {}
IService接口中的常用方法
方法 | 作用 |
---|---|
list() | 无条件查询所有 |
list(Wrapper wrapper) | 条件查询素有 |
page(Page page) | 无条件分页查询,Page是分页模型对象 |
page(Page page,Wrapper wrapper) | 条件分页查询,Page是分页模型对象 |
getById(Serializable id) | 根据主键查询单个对象 |
getOne(Wrapper wrapper) | 条件查询单个对象 |
save(T entity) | 添加单个对象 |
save(Collection col) | 批量添加对象的集合 |
updateById(T entity) | 修改,参数至少有一个属性值和主键 |
saveOrUpdate(T entity) | 添加或修改。如果实参对象的主键值不存在则添加,存在则修改 |
update(T entity,Wrapper wrapper) | 条件修改,条件为null则修改全部数据 |
removeById(Serializable id/T entity) | 根据主键或包含主键的对象删除 |
removeBatchByIds(Collection ids) | 根据集合删除 |
remove(Wrapper wrapper) | 根据条件删除,条件为null则删除全部 |
测试
package com.hqyj.sbmp01;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hqyj.sbmp01.entity.Hero;
import com.hqyj.sbmp01.mapper.HeroMapper;
import com.hqyj.sbmp01.service.HeroService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;@SpringBootTest
class IServiceTest {@Autowiredprivate HeroService heroService;@Testvoid test1() {//无条件查询所有//heroService.list().forEach(System.out::println);//条件查询,如果条件为null表示查询所有//heroService.list(null).forEach(System.out::println);//根据主键查询//Hero hero = heroService.getById(22);//System.out.println(hero);//根据条件查询,如果条件为null表示查询所有,会导致异常//System.out.println(heroService.getOne(null));}@Testvoid test2() {//添加save(T entity);参数对象至少要有一个属性值//添加后,主键自增的值会自动赋值给主键属性/*Hero hero = new Hero();hero.setHeroName("cvb");hero.setSex("男");hero.setPrice(2000);hero.setPosition("战士");heroService.save(hero);System.out.println(hero);*///批量添加,参数为要添加的对象集合/* Hero h1 = new Hero(0, "1", "1", "1", 100, "1999-9-9");Hero h2 = new Hero(0, "2", "1", "1", 100, "1999-9-9");Hero h3 = new Hero(0, "3", "1", "1", 100, "1999-9-9");List<Hero> heroes = Arrays.asList(h1, h2, h3);heroService.saveBatch(heroes);*/Hero h1 = new Hero(0, "666", "1", "1", 100, "1999-9-9");//如果添加的对象主键值已存在执行修改,不存在则添加heroService.saveOrUpdate(h1);}@Testvoid test3(){//根据对象修改Hero h1 = new Hero(999, "修改后", "修改后", "女", 100, "1999-9-9");//如果对象主键值已存在执行修改,不存在则添加//heroService.saveOrUpdate(h1);//根据主键修改heroService.updateById(h1);//根据条件修改//heroService.update(null);//根据实体和条件修改,如果条件为空,会将所有数据修改为指定实体的数据//heroService.update(h1,null);}@Testvoid test4(){//根据主键或带有主键值的对象删除//heroService.removeById(1103302663);/*Hero hero = new Hero();hero.setId(1103302662);heroService.removeById(hero);*///批量删除//heroService.removeBatchByIds(Arrays.asList(77,88,99));//根据条件删除,条件为null则删除全部heroService.remove(null);}}
条件构造器Wrapper
BaseMapper和IService接口中有很多方法都有这个参数,表示一个条件构造器对象。
如果该参数实际传递的值为null,表示没有任何条件,
这个Wrapper是一个抽象类,如果想要带条件,就要创建一个该类的子类对象。
常用子类为QueryWrapper和UpdateWrapper,
查询是创建QueryWrapper对象,更新时创建UpdateWrapper,实际使用无区别。
Wrapper对象带参数
Wrapper<T> wrapper = new QueryWrapper(T entity);
QueryWrapper<T> wrapper = new QueryWrapper(T entity);
Wrapper构造方法的参数如果是一个实体对象,只要该对象的属性不为空,就会将所有属性用and拼接起来作为条件。
这种适合已知某个字段为某个值时使用。
@Test
void test1() {//创建实体对象,只带两个属性值Hero hero = new Hero();hero.setHeroName("瑞兹");hero.setSex("女");//创建一个带实体参数的条件构造器对象Wrapper<Hero> wrapper = new QueryWrapper<>(hero);System.out.println(heroService.getOne(wrapper));
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yizbWK9T-1676548026845)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209095105726.png)]
Wrapper对象不带参数
Wrapper<T> wrapper = new QueryWrapper();
QueryWrapper<T> wrapper = new QueryWrapper();
当条件构造器不带参数时,就需要自定义条件。
这种适用于自定义查询条件。
默认多个条件时,用and连接,如果条件之间要使用or,调用or()方法
指定或范围
@Test
void test2() {/** eq(String column,Object val) equals column = val* ne(String column,Object val) not equals column <> val* lt(String column,Object val) less then column < val* gt(String column,Object val) great then column > val* le(String column,Object val) less equals column <= val* ge(String column,Object val) great equals column >= val* between(String column Object val1,Object val2) between and column between val1 and bal2* notBetween(String column Object val1,Object val2) not between and column not between val1 and bal2* *///创建一个不带实体参数的条件构造器对象QueryWrapper<Hero> wrapper = new QueryWrapper<>();//自定义条件:指定值//查询性别为男的数据 eq equals =//wrapper.eq("sex","男");//查询性别不为男的数据 ne not equals <>//wrapper.ne("sex","男");//wrapper.between("price",1000,4000);//查询价格小于5000的战士//多个条件默认用and拼接/*wrapper.lt("price",5000);wrapper.eq("position","战士");*///查询性别为女或价格大于3000//or()方法会将前后的条件使用or拼接/*wrapper.gt("price",3000);wrapper.or();wrapper.eq("sex","女");*///查询性别为男的战士或价格大于3000的法师//支持链式写法wrapper.eq("sex","男").eq("position","战士").or().eq("position","法师").gt("price","3000");heroService.list(wrapper).forEach(System.out::println);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c1gIsBNh-1676548026846)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209102606876.png)]
空值和模糊查询
@Test
void test3() {/** isNull(String column) column is null* isNotNull(String column) column is not null* like(String column,Object val) column like '%val%'* likeLeft(String column,Object val) column like '%val'* likeRight(String column,Object val) column like 'val%'* notLike(String column,Object val) column not like '%val%'* notLikeLeft(String column,Object val) column not like '%val'* notLikeRight(String column,Object val) column not like 'val%'* */QueryWrapper<Hero> wrapper = new QueryWrapper<>();//字段为空//wrapper.isNull("shelf_date");//带有指定关键字//wrapper.like("name","琳");//以指定关键字结尾//wrapper.likeLeft("name","琳");//以指定关键字开头//wrapper.likeRight("name","伊");//查询名字中带有琳字且上架时间为空wrapper.like("name","琳").isNull("shelf_date");heroService.list(wrapper).forEach(System.out::println);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FUvSAhMg-1676548026846)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209104130810.png)]
集合和排序
@Testvoid test4() {/** 集合* in(String column,Object... vals) column in (val1,val2...)* in(String column,Collection vals) column in (val1,val2...)* notIn(String column,Object... vals) column not in (val1,val2...)* notIn(String column,Collection vals) column not in (val1,val2...)** 排序* orderByAsc(String column) order by column asc* orderByDesc(String column) order by column desc** */QueryWrapper<Hero> wrapper = new QueryWrapper<>();//职业为战士或法师//wrapper.eq("position","战士").or().eq("position","法师");//可变参数//wrapper.in("position","战士","法师");//集合//wrapper.in("position", Arrays.asList("战士","法师"));//所有男性角色,按价格降序wrapper.eq("sex","男").orderByDesc("price");heroService.list(wrapper).forEach(System.out::println);}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lkh0EDLG-1676548026847)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209105104809.png)]
分组和聚合函数(自定义查询)
分组通常会和聚合函数(count、sum、min、max、avg)一起使用,但MyBatisPlus中没有现成的聚合函数。
需要使用**select(String… fields)**自定义查询字段。
如查询每个每个位置的总数
条件构造器对象.select(“position”,“count(id)”),对应的sql查询部分为select “position”,“count(id)” from hero
@Test
void test() {/** select(String... fields) select field1,field2...* groupBy(String column) group by column* */QueryWrapper<Hero> wrapper = new QueryWrapper<>();//无参数时,查询所有字段//wrapper.select();//查询指定字段//wrapper.select("name");//使用聚合函数wrapper.select("count(id)");//查询不同性别的人数//select sex,count(id) from hero group by sex//wrapper.select("sex","count(id)");//根据指定字段分组//wrapper.groupBy("sex");//当前的sql语句查询的结果是一个订制数据,如查询总数只会得到一个数据,所以使用listMaps()方法//List<Map<String, Object>> maps = heroService.listMaps(wrapper);//根据定位分组,查询值为法师、战士的人数,按人数降序wrapper.select("position","count(id) as count").in("position","战士","法师").groupBy("position").orderByDesc("count");List<Map<String, Object>> maps = heroService.listMaps(wrapper);System.out.println(maps);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3xj8aEw-1676548026847)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209110021881.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ecySX50A-1676548026847)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209112235219.png)]
分页查询
MyBatisPlus中集成了分页功能,只需在项目的启动类中注入一个分页拦截器对象
/*注入一个MyBatisPlus提供的分页拦截器对象定义一个方法,在方法上加入@Bean注解,将该方法的返回值注入到Spring容器中*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){//创建一个MybatisPlus拦截器对象MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();//通过拦截器对象添加分页拦截器对象,设置数据库类型interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;
}
@Test
void test1(){//无条件分页查询page(Page pageInfo)//1.创建一个分页模型对象,设置泛型,参数为当前页和每页显示的记录数Page<Hero> pageInfo = new Page<>(1, 5);//2.调用IService中的page方法,参数为分页模型对象,返回值也是该对象pageInfo = heroService.page(pageInfo);//分页的相关信息都保存在分页模型对象pageInfo中System.out.println("总页数"+pageInfo.getPages());System.out.println("当前页"+pageInfo.getCurrent());System.out.println("每页显示的记录数"+pageInfo.getSize());System.out.println("总记录数"+pageInfo.getTotal());System.out.println("分页后的集合"+pageInfo.getRecords());System.out.println("是否有下一页"+pageInfo.hasNext());System.out.println("是否有上一页"+pageInfo.hasPrevious());//条件分页page(Page pageInfo,Wrapper wrapper)//分页查询所有法师pageInfo= heroService.page(pageInfo,new QueryWrapper<Hero>().eq("position","法师"));pageInfo.getRecords().forEach(System.out::println);
}
代码生成器
MyBatisPlus中用于自动生成entity、mapper、service、controller这些类和接口的工具
1.添加依赖
<!-- 代码生成器 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.3</version>
</dependency>
<!--代码生成器所需引擎-->
<dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.3</version>
</dependency>
2.代码生成器类
修改以下几处
- 数据库信息
- 输出目录为本地硬盘目录
- 父包名
- 要生成的表名
package com.hqyj.question_sys.util;import com.baomidou.mybatisplus.generator.FastAutoGenerator;public class CodeGenerator {public static void main(String[] args) {//***数据库的url, 用户名和密码FastAutoGenerator.create("jdbc:mysql://localhost:3306/question_sys?serverTimezone=Asia/Shanghai","root", "root").globalConfig(builder -> {builder.author("HQYJ") // 设置作者,生成文件的注释里的作者.outputDir("F:\\框架\\question_sys\\src\\main\\java"); //***指定输出目录}).packageConfig(builder -> {builder.parent("com.hqyj.question_sys"); //***设置父包名// .controller("controller") //控制层包名// .service("service") //service接口包名// .serviceImpl("service.impl") //service接口实现包名// .mapper("mapper") //mapper接口包名// .xml("mapper.xml") //映射文件的包名(如果要生成的话,加上这句,去掉下面的.xml方法)// .entity("entity"); //实体类的包名}).strategyConfig(builder -> {builder.addInclude("question_catalog","question") //***设置需要生成的表名.controllerBuilder();//只生成Controller//.enableRestStyle(); //生成的Controller类添加@RestController;}).templateConfig(builder -> {builder.xml(null); //禁止生成xml映射文件}).execute();}
}
运行该类即可自动生成
MyBatisPlus中的逻辑删除和物理删除
物理删除:将数据真正删除。delete from 表 where id=值
逻辑删除:数据不删除,而是打个"被删除"的标记。额外添加一个是否被删除的字段"deleted",其值为0或1,0表示未删除,1表示已删除。
当删除某条数据时,执行修改 update 表 set deleted=1 where id=值
添加了"deleted"字段后,查询要更改为select * from 表 where deleted=0
实现过程
1.在表中添加一个新字段
deleted:默认为0表示未删除,1表示已删除
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QOCHl702-1676548026848)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209171301480.png)]
2.在实体类中对应属性,添加@TableLogic注解
@TableLogic//该注解表示该字段是逻辑删除字段
private Integer deleted;
此时调用IService接口中的list()方法查询所有时,会在条件中加入deleted=0
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ejE8Xe5w-1676548026848)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209171655736.png)]
此时调用IService接口中的删除时,会执行修改
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8NijaP96-1676548026848)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209171854655.png)]
Thymeleaf
SpringBoot官方建议使用的页面模板引擎,代替之前的JSP。
它是以HTML为基础,可以展示静态数据,方便前端人员开发静态页面,
也可以通过Thymeleaf的语法,配合EL输出由控制器跳转而来的动态数据。
Thymeleaf从入门到精通 – 知乎 (zhihu.com)
用法
1.添加依赖
<!--thymeleaf页面模板引擎-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId><version>2.7.3</version>
</dependency>
2.在temlates目录下创建一个HTML页面
在页面的HTML标签中,加入xmlns:th="http://www.thymeleaf.org"
属性。
可以通过修改IDEA中的HTML页面模板,之后每次创建HTML页面都会有该属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gtk3shs1-1676548026849)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209143658152.png)]
3.thymeleaf具体使用
-
双标签中的输出变量: th:text
<h1 th:text="${作用域中某个对象名}"></h1><h1 th:text="${username}"></h1>
-
单标签中设置value的值:th:value
<input type="text" th:value="${作用域中某个对象名}"><input type="text" th:value="${变量.属性名}">
-
遍历:th:each
<table><tr th:each="变量:${作用域中的集合名}"><td th:text="${变量.属性名}"></td></tr> </table><table><tr th:each="hero:${heroList}"><td th:text="${hero.id}"></td><td th:text="${hero.name}"></td><td th:text="${hero.sex}"></td></tr> </table>
-
超链接获取全局路径:th:href=“@{/}”
-
不带参数
<link th:href="@{/bootstrap-3.4.1-dist/css/bootstrap.css}" rel="stylesheet">
-
带参数
<a th:href="@{'/hero/delete?id='+${hero.id}">删除</a><a th:href="@{/question/findById(id=${question.quesCode})}">编辑</a>
-
-
表单提交路径:th:action=“@{/}”
<form methos="post" th:action="@{/hero/insert}"></form>
-
判断:th:if
<h1 th:if="判断逻辑">用户信息</h1><h1 th:if="${userinfo.username==null}">请登录</h1>
-
选中单选按钮或复选框:th:checked
<input type="radio" value="男" name="sex" th:checked="逻辑判断"><input type="radio" value="男" name="sex" th:checked="${user.sex=='男'}">男 <input type="radio" value="女" name="sex" th:checked="${user.sex eq '女'}">女
-
选中下拉菜单:th:selected
<select><option th:selected="逻辑判断"></option> </select><select><option th:selected="${book.typeId==bookType.typeId}" th:each="bookType:${list}" th:value="${bookType.typeId}" th:text="${bookType.typeName}"></option> </select>
-
src属性:th:src
<script th:src="@{/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js}"></script>
-
内联表达式
Thymeleaf页面中使用[[]]可以在script标签中使用EL表达式,这两对中括号拼接的内容称为内联表达式。
在给script标签加入**th:inline=“javascript”**后使用
<script th:inline="javascript">$(function () {//遍历页码$(".pno").each(function (){//如果页码和当前页一致,高亮显示if($(this).text()==[[${pageInfo.current}]]){$(this).addClass("active");}});}); </script><script th:inline="javascript">$(function () {//使用ajax读取所有的题目类型对象,遍历成option$.ajax({url:[[@{/qc/queryAll}]],success:function (res){for(var i=0;i<res.length;i++){let qcId = res[i].qcId;let qcName = res[i].qcName;$("<option></option>").text(qcName).val(qcId).appendTo($("select[name=qcId]"));}}});}); </script>
单表条件分页
原理:调用IService接口中的**page(Page page,Wrapper wrapper)**方法,创建分页模型对象和条件构造器对象
在Springboot启动类中加入mybatis分页拦截器
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor; }
搜索框
<form class="navbar-form navbar-left" th:action="@{/question/queryAll}"><div class="form-group"><input type="text" class="form-control" name="keyword" placeholder="请输入题目关键字"></div><button type="submit" class="btn btn-default">搜索</button> </form>
控制层
@RequestMapping("/queryAll") public String queryAll(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "5") Integer size,@RequestParam(defaultValue = "") String keyword,Model model) {//1.创建分页模型对象Page<Question> pageInfo = new Page<>(page, size);//创建条件构造器对象QueryWrapper<Question> wrapper = new QueryWrapper<>();wrapper.like("ques_title",keyword);//2.调用条件分页查询的方法questionService.page(pageInfo,wrapper);// 将分页模型对象保存到请求中model.addAttribute("pageInfo", pageInfo);//构造页数List<Long> pageList = new ArrayList<>();//每次最多显示5页//定义最大数字long maxPage = page + 4 > pageInfo.getPages() ? pageInfo.getPages() : page + 4;//定义最小数字long minPage = page - 4 < 1 ? page : maxPage - 4;for (long i = minPage; i <= maxPage; i++) {pageList.add(i);}model.addAttribute("pageList", pageList);return "questionList"; }
分页组件
<nav aria-label="Page navigation"><ul class="pagination"><li><a th:if="${pageInfo.hasPrevious}" th:href="@{/question/queryAll(keyword=${param.keyword},page=${pageInfo.current-1})}"aria-label="Previous"><span aria-hidden="true">«</span></a></li><li class="pno" th:each="pno:${pageList}"><a th:href="@{/question/queryAll(keyword=${param.keyword},page=${pno})}" th:text="${pno}"></a></li><li><a th:if="${pageInfo.hasNext}" th:href="@{/question/queryAll(keyword=${param.keyword},page=${pageInfo.current+1})}"aria-label="Next"><span aria-hidden="true">»</span></a></li></ul> </nav>
MyBatisPlus关联查询
可以通过创建xml文件来实现,过程同MyBatis关联查询。
这里使用注解实现。
主表question_catalog
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nc9I9gWS-1676548026849)(D:/qqFileRecv/框架/框架/day15-SpringBoot+MyBatisPlus3.assets/image-20230210142342982.png)]
从表question
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IshUMlt8-1676548026849)(D:/qqFileRecv/框架/框架/day15-SpringBoot+MyBatisPlus3.assets/image-20230210142322936.png)]
多对一
查询时以从表数据为主体,关联对应的主表信息。
多个question对象对应一个question_catalog对象。
在查询question表的同时,显示关联question_catalog表中的数据。
实体类
在从表的实体类中,额外添加一个外键对应的主表实体对象
@Data public class Question implements Serializable {private static final long serialVersionUID = 1L;@TableId(type = IdType.ASSIGN_UUID)private String quesCode;private Integer qcId;private String quesTitle;private String quesOptA;private String quesOptB;private String quesOptC;private String quesAns;@TableLogic//该注解表示该字段是逻辑删除字段private Integer deleted;/** 如果在实体类中添加了一个不属于该表中字段的属性* 要在该属性上加@TableField(exist = false)表示该属性没有对应的字段* */@TableField(exist = false)private QuestionCatalog qc; }
原理
如果直接通过IService接口中的list()方法查询,实际调用的是BaseMapper接口中的selectList()方法,默认查询自身表。
如果重写业务层中的list()方法,或在业务层中自定义一个方法,让其调用数据访问层中自定义的某个方法,重新定制sql语句,就能得到想要的数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zc2Egb23-1676548026850)(D:/qqFileRecv/框架/框架/day15-SpringBoot+MyBatisPlus3.assets/image-20230210145612843.png)]
实现过程
1.在Mapper(QuestionMapper)接口中自定义一个方法,使用注解编写sql语句
有两种方式:
-
方式一:构造自定义的sql语句
- 建议外键关联字段比较少的情况下使用
- sql语句需要连接查询,将外键关联的表的字段重命名为"外键实体对象名.属性名"
@Select("select q.*,qc_name as 'qc.qcName' from question q,question_catalog qc where q.qc_id=qc.qc_id ") List<Question> mySelect();
-
方式二:使用子查询,自定义结果集映射
- 建议外键关联字段比较多的情况下使用
- 先查询从表,再使用外键字段查询主表
- 外键字段需要重新映射一次
//1.查询自身表 @Select("select * from question") //2.自定义结果集映射 @Results({//qc_id字段在下面的子查询中已经使用了,将外键字段qc_id重新映射@Result(property = "qcId",column = "qc_id"),//遇到qc属性,使用qc_id进行子查询//执行QuestionCatalogMapper中的selectById方法//该方法是BaseMapper接口中默认就存在的方法@Result(property ="qc" ,column ="qc_id",one =@One(select = "com.hqyj.question_sys.mapper.QuestionCatalogMapper.selectById")) }) List<Question> mySelect();
2.在Service层中调用Mapper层中自定义的方法
@Service public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements IQuestionService {/** 如果直接调用该方法查询,会最终调用BaseMapper中的selectList()方法* 这里重写该方法,让其调用BaseMapper中的自定义方法* */@Overridepublic List<Question> list() {return baseMapper.mySelect();} }
一对多
以主表为主体,查询时显示关联的从表对象集合。
查询question_catalog表,同时显示qc_id对应的question表中的数据集合。
实体类
在主表实体类中,额外添加一个从表实体对象集合。
@TableName("question_catalog") @Data public class QuestionCatalog implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "qc_id", type = IdType.AUTO)private Integer qcId;private String qcName;/** 一对多查询,定义关联的从表对象集合* @TableField(exist = false)标明该属性没有对应的字段* */@TableField(exist = false)private List<Question> questionList; }
原理同多对一查询,重写service层中的list()方法,改变原本方法中调用的内容
实现过程
1.在Mapper(QuestionCatalogMapper)接口中自定义一个方法,使用注解编写sql语句
/** 自定义查询* 使用子查询实现一对多* */ //1.查询question_catalog表 @Select("select * from question_catalog") //2.自定义结果集映射 @Results({//由于在下面使用qc_id进行了子查询,对其重新映射@Result(property = "qcId",column = "qc_id"),//3.使用qc_id进行子查询,查询question表,调用根据qc_id查询对应数据集合的方法,没有现成的方法,需要自定义@Result(property = "questionList", column = "qc_id", many = @Many(select = "com.hqyj.question_sys.mapper.QuestionMapper.queryByQcId")) }) List<QuestionCatalog> mySelect();
2.在Mapper(QuestionMapper)接口中定义方法
/** 根据qc_id查询对应数据集合* */ @Select("select * from question where qc_id=#{qcId}") List<Question> queryByQcId();
3.在Service层中调用Mapper层中自定义的方法
/** 重写原本的查询所有,执行自定义的查询* */ @Override public List<QuestionCatalog> list() {return baseMapper.mySelect(); }
多对一关联查询条件分页
原理
参考原本的条件分页查询方法
selectPage(P page, Wrapper<Question> queryWrapper)
page用于分页,queryWrapper用于构造条件。通过自定义的sql语句关联两张表实现。
在自定义的sql语句中,通过ew.sqlSegment或{ew.sqlSegment}或ew.sqlSegment或{ew.customSqlSegment}使用条件构造器Wrapper对象
${ew.sqlSegment} 将条件原样拼接
${ew.customSqlSegment} 在所有条件前自动加入where关键字
实现过程
1.在Mapper(QuestionMapper)中自定义关联查询的sql
- ${ew.sqlSegment} 将条件原样拼接
- ${ew.customSqlSegment} 在所有条件前加入where关键字
//${ew.sqlSegment} 将条件原样拼接 //@Select("SELECT q.*,qc_name AS 'qc.qcName' FROM question q,question_catalog qc WHERE q.qc_id = qc.qc_id and ${ew.sqlSegment}")//${ew.customSqlSegment} 在所有条件前加入where关键字 @Select("SELECT q.*,qc_name AS 'qc.qcName' FROM question q inner join question_catalog qc on q.qc_id = qc.qc_id ${ew.customSqlSegment}") <P extends IPage<Question>> P queryByCondition(P page, @Param("ew") Wrapper<Question> queryWrapper);
2.在service层中重写或自定义方法,调用mapper层中自定义的方法
@Override public <E extends IPage<Question>> E page(E page, Wrapper<Question> queryWrapper) {return baseMapper.queryByCondition(page,queryWrapper); }
3.controller层
- 接收所需参数
- 创建分页模型对象
- 创建条件构造器对象
@RequestMapping("/queryAll") public String queryAll(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "5") Integer size,@RequestParam(defaultValue = "") String keyword,@RequestParam(defaultValue = "0") Integer qcId,Model model) {//1.创建分页模型对象Page<Question> pageInfo = new Page<>(page, size);//创建条件构造器对象QueryWrapper<Question> wrapper = new QueryWrapper<>();wrapper.like("ques_title", keyword);if (qcId != 0) {wrapper.eq("q.qc_id", qcId);}//2.调用条件分页查询的方法questionService.page(pageInfo, wrapper);model.addAttribute("pageInfo", pageInfo);//构造页数List<Long> pageList = new ArrayList<>();//每次最多显示5页//定义最大数字long maxPage = page + 4 > pageInfo.getPages() ? pageInfo.getPages() : page + 4;//定义最小数字long minPage = page - 4 < 1 ? page : maxPage - 4;for (long i = minPage; i <= maxPage; i++) {pageList.add(i);}model.addAttribute("pageList", pageList);return "questionList"; }
4.页面表单
<form class="navbar-form navbar-left" th:action="@{/question/queryAll}"><div class="form-group"><input type="text" class="form-control" name="keyword" placeholder="请输入题目关键字"><select class="form-control" name="qcId"></select></div><button type="submit" class="btn btn-default">搜索</button> </form> <script th:inline="javascript">$(function () {$.ajax({url:[[@{/qc/queryAll}]],success:function (res){for(var i=0;i<res.length;i++){let qcId = res[i].qcId;let qcName = res[i].qcName;$("<option></option>").text(qcName).val(qcId).appendTo($("select[name=qcId]"));}}});}); </script>
-
答题功能核心内容
随机出题
/** 随机5条记录* */
@Select("select * from question order by rand() limit 5 ")
List<Question> queryByRand();
答题页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"><head><title>Title</title><link rel="shortcut icon" href="https://www.baidu.com/favicon.ico" type="image/x-icon"><!--bootstrap的css文件路径--><link th:href="@{/bootstrap-3.4.1-dist/css/bootstrap.css}" rel="stylesheet"></head><body><div class="col-md-4"></div><div class="col-md-4"><form id="result"><div class="panel panel-primary" th:each="question:${list}"><div class="panel-heading" th:text="${question.quesTitle}"></div><div class="panel-body"><div class="form-group"><label><input type="radio" value="a" th:name="${question.quesCode}"><span th:text="${question.quesOptA}"></span></label><label><input type="radio" value="b" th:name="${question.quesCode}"><span th:text="${question.quesOptB}"></span></label><label><input type="radio" value="c" th:name="${question.quesCode}"><span th:text="${question.quesOptC}"></span></label></div></div></div><button class="btn btn-default loginBtn" type="submit">提交</button></form></div><div class="col-md-4"></div><script th:src="@{/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js}"></script><script th:inline="javascript">$(function () {$("#result").submit(function () {//创建数组保存答题结果var ansList = [];//获取表单的数据 表单对象.serialize()可以获取表单中每个name-value,将其拼接为字符串var data = $(this).serialize();//name=value&name=value//切分答题结果let list = data.split("&");//遍历答题结果,将其构造为一个答题结果对象for (var i = 0; i < list.length; i++) {//创建一个对象var ans = {};//构造对象的属性ans.code = list[i].split("=")[0];ans.opt = list[i].split("=")[1];ansList.push(ans);}//提交ansList$.ajax({url: [[@{/question/result}]],data: {ansList: JSON.stringify(ansList)},traditional: true,success: function (res) {alert("你答对了"+res+"道题");}});return false;})});</script></body>
</html>
答题
答题对象
package com.hqyj.question_sys.entity;import lombok.Data;/*
* 创建答题结果对象
* */
@Data
public class AnswerResult {private String code;private String opt;
}
controller
@RequestMapping("/result")
@ResponseBody
public Integer result(String ansList) throws JsonProcessingException {//将JSON格式的字符串转换为对象//SpringBoot中集成了jackson,可以用于对象和JSON之间的转换//创建ObjectMapper对象ObjectMapper jsonTool = new ObjectMapper();//读取接收到的JSON字符串,转换为Result对象数组AnswerResult[] list = jsonTool.readValue(ansList, AnswerResult[].class);//遍历答题结果Integer count=0;for (AnswerResult res : list) {/*//答题题目String code = res.getCode();//回答结果String opt = res.getOpt();//原本题目Question question = questionService.getById(code);//真正答案String quesAns = question.getQuesAns();*/if (questionService.getById(res.getCode()).getQuesAns().equals(res.getOpt())) {count++;}}return count;
}
Spring Data JPA
2001年推出了Hibernate,是一个全自动ORM框架。可以不用编写SQL语句,就能实现对数据库的持久化操作。
SUN公司在Hibernate的基础上,制定了JPA,全称 Java Persisitence API,中文名Java持久化API,
是一套Java访问数据库的规范,由一系列接口和抽象类构成。
后来Spring团队在SUN公司制定的JPA这套规范下,推出了Spring Data JPA,是JPA的具体实现。
如今常说的JPA,通常指Spring Data JPA。
SpringBoot集成Spring Data JPA
1.创建SpringBoot项目,选择依赖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VHccTb66-1676548026850)(D:/qqFileRecv/框架/框架/day16-SpringBoot+JPA.assets/image-20230213141450488.png)]
2.编辑配置文件,设置要连接的数据库信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/bookdb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root# 设置数据库类型
spring.jpa.database=mysql
# 打印SQL语句
spring.jpa.show-sql=true
3.创建实体类
-
类上加**@Entity**注解
-
主键属性上加
-
@Id注解标明主键
-
**@GeneratedValue(strategy = GenerationType.IDENTITY)**设置MySQL数据库主键生成策略,数据库设置为自增
-
-
其他属性名与字段名一致或驼峰命名法
- 如果字段名多个单词之间用_,使用驼峰命名法
- 如果不相同,使用**@Column(name=“字段名”)**注解指定该属性对应的字段名
@Data
@Entity
/*
* 实体类的属性名建议使用驼峰命名法
* */
public class BookInfo {@Id//主键字段//主键生成策略,GenerationType.IDENTITY表示MySQL自增@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer bookId;private Integer typeId;private String bookName;private String bookAuthor;//如果字段名和属性名不一致,使用@Column指定字段名@Column(name = "book_price")private Integer price;private Integer bookNum;private String publisher_date;
}
4.数据访问层接口
- 类上加@Repository注解
- 继承JpaRepository<实体类型,主键类型>接口
@Repository
public interface BookInfoDao extends JpaRepository<BookInfo,Integer> {}
5.测试常用方法
方法名 | 返回值 | 作用 |
---|---|---|
findAll() | List | 查询所有数据。 |
save(T entity) | T entity | 添加或修改。如果不存在主键属性或主键值不存在,执行添加;如果存在主键属性且有该主键值,执行修改。 |
delete(T entity) | void | 根据对象删除。如果该对象中有主键属性且有该主键值,根据该主键值删除。 |
findById(主键) | Optional | 根据主键值查询。返回的对象调用isPresent()结果为true,表示查询到了数据,继续调用get()得到查询到的对象。 |
@SpringBootTest
class Day16SpringBootJpaApplicationTests {@Autowiredprivate BookInfoDao bookInfoDao;@Testvoid contextLoads() {List<BookInfo> all = bookInfoDao.findAll();all.forEach(System.out::println);}@Testvoid insert() {BookInfo bookInfo = new BookInfo();bookInfo.setBookName("测试");bookInfo.setBookAuthor("测试");bookInfo.setBookNum(156);bookInfo.setPrice(20);bookInfo.setTypeId(1);//save()方法调用时,如果对象中没有主键或主键值不存在,作为添加使用BookInfo save = bookInfoDao.save(bookInfo);//添加成功后,会自动获取主键自增的值System.out.println(save);}@Testvoid update() {BookInfo bookInfo = new BookInfo();bookInfo.setBookName("xxxxxxxxxxx");bookInfo.setBookAuthor("测试");bookInfo.setBookNum(356);bookInfo.setPrice(23);bookInfo.setTypeId(1);//save()方法调用时,如果对象中有主键且存在,作为修改使用BookInfo save = bookInfoDao.save(bookInfo);//修改成功后,返回修改后的对象System.out.println(save);}@Testvoid delete() {//根据主键值删除,如果值不存在,会报错//bookInfoDao.deleteById(36);//根据对象删除,如果对象中包含主键值则删除,如果没有值或不存在,不会报错BookInfo bookInfo = new BookInfo();//bookInfo.setBookId(330);bookInfoDao.delete(bookInfo);}@Testvoid findOne() {//根据主键查询,返回值Optional类型Optional<BookInfo> byId = bookInfoDao.findById(60);//isPresent()如果为true表示查询到了数据if (byId.isPresent()) {//get()将查询到的数据转换为对应的实体类BookInfo bookInfo=byId.get();System.out.println(bookInfo);}else{System.out.println("未查询到数据");}}
}
JPA进阶
分页查询
调用数据访问层中的**findAll(Pageable pageable)**方法,即可实现分页。
参数Pageable是org.springframework.data.domain包中的一个接口,通过其实现类
PageRequest,调用静态方法of(int page,int size),当做Pageable对象使用。
这里的page从0开始为第一页。
@Test
void queryByPage(){//PageRequest是Pageable的实现类,调用静态方法of(int page,int size)//这里的page的值0表示第一页//调用findAll(Pageable pageable)方法,返回分页模型对象Page<BookInfo> pageInfo = bookInfoDao.findAll(PageRequest.of(0,5));//分页相关数据System.out.println("总记录数"+pageInfo.getTotalElements());System.out.println("最大页数"+pageInfo.getTotalPages());System.out.println("分页后的数据集合"+pageInfo.getContent());System.out.println("当前页数"+pageInfo.getNumber());System.out.println("每页显示的记录数"+pageInfo.getSize());System.out.println("是否还有下一页"+pageInfo.hasNext());System.out.println("是否还有上一页"+pageInfo.hasPrevious());
}
条件查询
在JPA中,使用自定义方法名自动生成对应的SQL语句,实现条件查询。
如在dao中定义了queryById(int id)方法,就表示根据id查询,自动生成sql语句。
方法命名格式
[xxx] [By] [字段对应的属性名] [规则] [Or/And] [字段对应的属性名] [规则] …
- **xxx可以是find、get、query、search
- 方法如果有参数,参数的顺序和方法名中的参数顺序一致
如findByBookNameAndBookAuthor(String bookName,String bookAuthor),
对应的sql语句为 select * from book where book_name =? and book_author=?
常用规则
规则 | 方法名 | SQL中的条件 |
---|---|---|
指定值 | findByBookName(String name) | book_name = name |
Or/And | findByBookNameOrBookAuthor(String name,String author) | book_name = name or book_author = author |
After/Befor | findByBookPriceAfter(double price) | book_price > price |
GreaterThanEqual/LessThanEqual | findByBookNumLessThanEqual(int num) | book_num <= num |
Between | findByBookNumBetween(int min,int max) | book_num between min and max |
Is[Not]Null | findByPublisherDateIsNull() | publish_date is null |
[Not]Like | findByBookNameLike(String condition) | book_name like ‘condition’ |
[Not]Contains | findByBookNameContains(String keyword) | book_name like ‘%keyword%’ |
StartsWith/EndsWith | findByBookNameStartsWith(String firstName) | book_name like ‘firstName%’ |
无条件排序:findAllByOrderBy字段[Desc/Asc] | findAllByOrderByBookId() | order by book_id asc |
有条件排序:findAllBy条件OrderBy字段[Desc/Asc] | findAllByTypeIdOrderByBookIdDesc() | type_id = ? order by book_id desc |
@Repository
public interface BookInfoDao extends JpaRepository<BookInfo,Integer> {//指定值查询//根据书名查询List<BookInfo> getAllByBookName(String x);//查询价格大于指定值 字段对应的属性名 After/GreaterThanList<BookInfo> findAllByPriceAfter(int price);//查询价格小于于指定值 字段对应的属性名 Before/LessThanList<BookInfo> findAllByPriceLessThan(int price);//查询库存大于等于指定值 GreaterThanEqualList<BookInfo> queryAllByBookNumGreaterThanEqual(int num);//查询库存在指定闭区间内 Between(int min,int max)List<BookInfo> findAllByBookNumBetween(int min,int max);//空值查询 null//查询出版日期为空 IsNull/IsNotNullList<BookInfo> findAllByPublisherDateIsNull();//书名中带有关键字 Like/NotLike 实参一定要使用%或_List<BookInfo> getAllByBookNameLike(String keyword);//作者名中带有关键字 Contains/NotContains 实参只需要关键字List<BookInfo> getAllByBookAuthorContains(String keyword);//指定作者的姓 指定开头/结尾 StartsWith/EndsWithList<BookInfo> getAllByBookAuthorStartsWith(String keyword);//查询所有数据,按价格降序 无条件排序 OrderBy字段[Desc/Asc]List<BookInfo> getAllByOrderByPriceDesc();//查询指定类型,按id降序List<BookInfo> getAllByTypeIdOrderByBookIdDesc(Integer typeId);}
条件分页查询
只需在定义方法时,将方法的返回值设置为Page类型,在参数中就如Pageable对象
//根据作者和书名的关键字分页查询
Page<BookInfo> getAllByBookNameContainsOrBookAuthorContains(String nameKeyword,String authorKeyword,Pageable pageable);
Page<BookInfo> pageInfo = bookInfoDao.getAllByBookNameContainsOrBookAuthorContains("龙","山",PageRequest.of(0,5));
//分页相关数据保存在pageInfo对象中
聚合函数分组查询
自定义SQL
在数据访问层接口中的方法上,可以加入@Query注解,默认要使用HQL(Hibernate专用)格式的语句。
如果要使用原生的SQL语句,需要添加nativeQuery=true属性,用value属性定义SQL语句
/** 在JPA中,如果要使用自定义的SQL语句* nativeQuery = true 开启原生SQL语句* value="sql语句"* */
@Query(nativeQuery = true, value = "select book_author,count(book_id) from book_info group by book_author")
List testQuery();
@Test
void test(){List list = bookInfoDao.testQuery();//查询的结果为集合,集合中保存的是每一行数据for (Object row : list) {//每一行页数一个对象数组Object[] obj= (Object[])row;//根据索引得到查询出的内容System.out.println(obj[0]+"---"+obj[1]);}
}
自定义SQL中带参数
SQL语句中的":XXX"表示参数
如果方法的形参名和xxx一致时直接使用,如果不一致,在形参上加入@Param注解设置形参名
/** 根据作者查询其图书总库存* 使用":形参名"在SQL语句中带参数* 在方法中通过@Prama定义形参* */
@Query(nativeQuery = true, value = " select book_author,sum(book_num) from book_info where book_author=:zuozhe")
List testQuery3(@Param("zuozhe") String xxx);
@Test
void test(){List list = bookInfoDao.testQuery3("金庸");//查询的结果为集合,集合中保存的是每一行数据for (Object row : list) {//每一行页数一个对象数组Object[] obj= (Object[])row;//根据索引得到查询出的内容System.out.println(obj[0]+"---"+obj[1]);}
}
关联查询
主表book_type
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZCwFQTbl-1676548026851)(D:/qqFileRecv/框架/框架/day16day17-SpringBoot+JPA.assets/image-20230214105234875.png)]
从表book_info
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sM1NxWG1-1676548026851)(D:/qqFileRecv/框架/框架/day16day17-SpringBoot+JPA.assets/image-20230214105247332.png)]
实体类
主表实体BookType
@Entity
@Data
public class BookType {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer typeId;private String typeName;
}
从表实体BookInfo
- 无需写出外键字段属性
- 额外添加外键字段对应的实体类对象属性
@Data
@Entity
public class BookInfo {@Id//主键字段//主键生成策略,GenerationType.IDENTITY表示MySQL自增@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer bookId;//private Integer typeId;private String bookName;private String bookAuthor;//如果字段名和属性名不一致,使用@Column指定字段名@Column(name = "book_price")private Integer price;private Integer bookNum;private String publisherDate;//多对一查询,以当前从表信息为主体,关联相应的主表信息@JoinColumn(name = "type_id")//使用type_id字段进行多对一查询@ManyToOne//多对一private BookType bt;
}
使用
-
多对一查询,调用dao中的查询方法,就会自动给外键字段对应的对象赋值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e6GbVH67-1676548026851)(D:/qqFileRecv/框架/框架/day16day17-SpringBoot+JPA.assets/image-20230214111732161.png)]
-
添加时,参数所需外键字段,使用外键字段对应的对象名代替
//添加 @PostMapping("/book") public RestResult<BookInfo> insert(BookInfo bookInfo) {return RestResult.ok("添加成功", bookInfoDao.save(bookInfo)); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-diRNCC1k-1676548026852)(D:/qqFileRecv/框架/框架/day16day17-SpringBoot+JPA.assets/image-20230214113125279.png)]
-
如果根据外键字段查询,要在从表dao中定义方法findBy外键对象名_外键对象属性名(数据类型 外键字段)
//如果根据外键字段查询,方法名写为 By外键实体类属性名_属性名 List<BookInfo> getAllByBt_TypeId(Integer typeId);
前后端分离项目
前后端分离,就是将web应用中的前端页面和后端代码分开完成、部署。
- 前后端的开发者只需要完成各自的事情,最终以文档的形式约定数据接口(URL、参数、返回值、请求方式)
- 前后端分别用独立的服务器
- 后端只需处理数据并提供访问接口(路径),以RESTFul风格的JSON格式传输数据
- 前端只需负责渲染页面和展示数据
传统项目和前后端分离项目对比
传统项目
前端和后端的代码运行在一个服务器上,页面经由控制器跳转
SSM项目、图书管理系统、答题系统
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fthntOJP-1676548026852)(D:/qqFileRecv/框架/框架/day16-SpringBoot+JPA.assets/image-20230213170658853.png)]
前后端分离项目
前后端的代码分别运行在各自的服务器上
后端提供JSON格式字符串的数据接口
前端负责跳转、解析JSON数据。
酒店客房管理系统
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n5XClYA1-1676548026852)(D:/qqFileRecv/框架/框架/day16-SpringBoot+JPA.assets/image-20230213171038191.png)]
前后端分离项目后端控制层设计
请求方式设计:RESTFul风格
风格,不是标准,可以不用强制遵循。
RESTFul风格:用不同的请求方式去访问同一个URL地址时,执行不同的操作。
特点
- 通过URL就能知道当前在哪个模块
- 通过不同的请求方式决定执行什么操作
- 通过返回的状态码得到操作结果
使用RESTFul风格和普通方式对比
普通方式
localhost:8080/user/queryAll 查询所有
localhost:8080/user/queryById?id=1001 条件查询
localhost:8080/user/insert?name=ez&sex=男&age=20 添加
localhost:8080/user/update?name=ez&id=1001 修改
localhost:8080/user/delete?id=1001 删除
RESTFul风格
localhost:8080/user 查询所有get请求
localhost:8080/user/1001 条件查询get请求
localhost:8080/user 添加post请求
localhost:8080/user 修改put请求
localhost:8080/user/1001 删除delete请求
RESTFul风格具体使用
-
在请求映射的命名上,统一用小写字母的名词形式表示当前位于哪个模块。如/user、/book_info
-
访问时如果要传参,使用"/模块名/参数"方式,配合controller中的@PathVariable获取
@GetMapping("/book/{id}") public BookInfo queryById(@PathVariable("id")Integer id){return service.findById(id); }
-
在controller的方法上,使用@XXXMapping()设置访问该方法的请求方式
- @GetMapping(“路径”) 查询
- @PostMapping(“路径”) 添加
- **@PutMapping(“路径”) ** 修改
- **@DeleteMapping(“路径”) ** 删除
- @RequestMapping(value=“路径”,method=RequestMethod.GET/POST/PUT/DELETE))
-
如果请求方式不匹配,会报405异常
-
在同一个controller中,不能出现两个请求方式和路径都一致的方法
返回值设计
前后端分离项目的控制层方法的返回值也需要进行统一。
返回值通常包含以下信息
- 传递状态,用状态码表示Integer code
- 传递消息,用字符串表示String msg
- 传递集合,用集合表示List list
- 传递对象,用对象表示Object obj
将这些信息封装到一个对象中,这个对象称为返回结果类RestResult对象
RestResult类具体设计
Postman
Postman API Platform | Sign Up for Free
Java技术栈
服务器端
JavaSE(API、OOP)、JavaEE(Servlet、Spring、SpringMVC)
tomcat
前端0
HTML+CSS+JS+JQUERY、Bootstrap、LayUI、EasyUI、VUE
数据库
MySQL、Oracle、Redis
JDBC、SpringJDBC、MyBatis、MyBatisPlus、JPA
工具
IDEA、HBuilder、VSCode、Notepad++、Sublime、Navicat、Postman
JavaEE过程
Servlet + JDBC + JSP(bootstrap) 图书商场
Spring + SpringMVC + SpringJDBC + JSP(bootstrap)
Spring + SpringMVC + MyBatis+ JSP 图书管理系统SSM
SpringBoot + MyBatis + html(LayUI) 酒店客房管理(可前后端分离)
SpringBoot + MyBatisPlus + html(Thymeleaf+bootstrap) 答题系统
SpringBoot + Spring Data JPA
项目一
SpringBoot+MyBatisPlus+Vue 仓库管理(前后端分离)
findAllByPriceAfter(int price);
//查询价格小于于指定值 字段对应的属性名 Before/LessThan
List<BookInfo> findAllByPriceLessThan(int price);//查询库存大于等于指定值 GreaterThanEqual
List<BookInfo> queryAllByBookNumGreaterThanEqual(int num);//查询库存在指定闭区间内 Between(int min,int max)
List<BookInfo> findAllByBookNumBetween(int min,int max);//空值查询 null
//查询出版日期为空 IsNull/IsNotNull
List<BookInfo> findAllByPublisherDateIsNull();//书名中带有关键字 Like/NotLike 实参一定要使用%或_
List<BookInfo> getAllByBookNameLike(String keyword);//作者名中带有关键字 Contains/NotContains 实参只需要关键字
List<BookInfo> getAllByBookAuthorContains(String keyword);//指定作者的姓 指定开头/结尾 StartsWith/EndsWith
List<BookInfo> getAllByBookAuthorStartsWith(String keyword);//查询所有数据,按价格降序 无条件排序 OrderBy字段[Desc/Asc]
List<BookInfo> getAllByOrderByPriceDesc();//查询指定类型,按id降序
List<BookInfo> getAllByTypeIdOrderByBookIdDesc(Integer typeId);
}
## 条件分页查询只需在定义方法时,将方法的返回值设置为Page类型,在参数中就如Pageable对象```java
//根据作者和书名的关键字分页查询
Page<BookInfo> getAllByBookNameContainsOrBookAuthorContains(String nameKeyword,String authorKeyword,Pageable pageable);
Page<BookInfo> pageInfo = bookInfoDao.getAllByBookNameContainsOrBookAuthorContains("龙","山",PageRequest.of(0,5));
//分页相关数据保存在pageInfo对象中
聚合函数分组查询
自定义SQL
在数据访问层接口中的方法上,可以加入@Query注解,默认要使用HQL(Hibernate专用)格式的语句。
如果要使用原生的SQL语句,需要添加nativeQuery=true属性,用value属性定义SQL语句
/** 在JPA中,如果要使用自定义的SQL语句* nativeQuery = true 开启原生SQL语句* value="sql语句"* */
@Query(nativeQuery = true, value = "select book_author,count(book_id) from book_info group by book_author")
List testQuery();
@Test
void test(){List list = bookInfoDao.testQuery();//查询的结果为集合,集合中保存的是每一行数据for (Object row : list) {//每一行页数一个对象数组Object[] obj= (Object[])row;//根据索引得到查询出的内容System.out.println(obj[0]+"---"+obj[1]);}
}
自定义SQL中带参数
SQL语句中的":XXX"表示参数
如果方法的形参名和xxx一致时直接使用,如果不一致,在形参上加入@Param注解设置形参名
/** 根据作者查询其图书总库存* 使用":形参名"在SQL语句中带参数* 在方法中通过@Prama定义形参* */
@Query(nativeQuery = true, value = " select book_author,sum(book_num) from book_info where book_author=:zuozhe")
List testQuery3(@Param("zuozhe") String xxx);
@Test
void test(){List list = bookInfoDao.testQuery3("金庸");//查询的结果为集合,集合中保存的是每一行数据for (Object row : list) {//每一行页数一个对象数组Object[] obj= (Object[])row;//根据索引得到查询出的内容System.out.println(obj[0]+"---"+obj[1]);}
}
关联查询
实体类
主表实体BookType
@Entity
@Data
public class BookType {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer typeId;private String typeName;
}
从表实体BookInfo
- 无需写出外键字段属性
- 额外添加外键字段对应的实体类对象属性
@Data
@Entity
public class BookInfo {@Id//主键字段//主键生成策略,GenerationType.IDENTITY表示MySQL自增@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer bookId;//private Integer typeId;private String bookName;private String bookAuthor;//如果字段名和属性名不一致,使用@Column指定字段名@Column(name = "book_price")private Integer price;private Integer bookNum;private String publisherDate;//多对一查询,以当前从表信息为主体,关联相应的主表信息@JoinColumn(name = "type_id")//使用type_id字段进行多对一查询@ManyToOne//多对一private BookType bt;
}
使用
-
多对一查询,调用dao中的查询方法,就会自动给外键字段对应的对象赋值
[外链图片转存中…(img-e6GbVH67-1676548026851)]
-
添加时,参数所需外键字段,使用外键字段对应的对象名代替
//添加 @PostMapping("/book") public RestResult<BookInfo> insert(BookInfo bookInfo) {return RestResult.ok("添加成功", bookInfoDao.save(bookInfo)); }
-
如果根据外键字段查询,要在从表dao中定义方法findBy外键对象名_外键对象属性名(数据类型 外键字段)
//如果根据外键字段查询,方法名写为 By外键实体类属性名_属性名 List<BookInfo> getAllByBt_TypeId(Integer typeId);
前后端分离项目
前后端分离,就是将web应用中的前端页面和后端代码分开完成、部署。
- 前后端的开发者只需要完成各自的事情,最终以文档的形式约定数据接口(URL、参数、返回值、请求方式)
- 前后端分别用独立的服务器
- 后端只需处理数据并提供访问接口(路径),以RESTFul风格的JSON格式传输数据
- 前端只需负责渲染页面和展示数据
传统项目和前后端分离项目对比
传统项目
前端和后端的代码运行在一个服务器上,页面经由控制器跳转
SSM项目、图书管理系统、答题系统
前后端分离项目
前后端的代码分别运行在各自的服务器上
后端提供JSON格式字符串的数据接口
前端负责跳转、解析JSON数据。
酒店客房管理系统
前后端分离项目后端控制层设计
请求方式设计:RESTFul风格
风格,不是标准,可以不用强制遵循。
RESTFul风格:用不同的请求方式去访问同一个URL地址时,执行不同的操作。
特点
- 通过URL就能知道当前在哪个模块
- 通过不同的请求方式决定执行什么操作
- 通过返回的状态码得到操作结果
使用RESTFul风格和普通方式对比
普通方式
localhost:8080/user/queryAll 查询所有
localhost:8080/user/queryById?id=1001 条件查询
localhost:8080/user/insert?name=ez&sex=男&age=20 添加
localhost:8080/user/update?name=ez&id=1001 修改
localhost:8080/user/delete?id=1001 删除
RESTFul风格
localhost:8080/user 查询所有get请求
localhost:8080/user/1001 条件查询get请求
localhost:8080/user 添加post请求
localhost:8080/user 修改put请求
localhost:8080/user/1001 删除delete请求
RESTFul风格具体使用
-
在请求映射的命名上,统一用小写字母的名词形式表示当前位于哪个模块。如/user、/book_info
-
访问时如果要传参,使用"/模块名/参数"方式,配合controller中的@PathVariable获取
@GetMapping("/book/{id}") public BookInfo queryById(@PathVariable("id")Integer id){return service.findById(id); }
-
在controller的方法上,使用@XXXMapping()设置访问该方法的请求方式
- @GetMapping(“路径”) 查询
- @PostMapping(“路径”) 添加
- **@PutMapping(“路径”) ** 修改
- **@DeleteMapping(“路径”) ** 删除
- @RequestMapping(value=“路径”,method=RequestMethod.GET/POST/PUT/DELETE))
-
如果请求方式不匹配,会报405异常
-
在同一个controller中,不能出现两个请求方式和路径都一致的方法
返回值设计
前后端分离项目的控制层方法的返回值也需要进行统一。
返回值通常包含以下信息
- 传递状态,用状态码表示Integer code
- 传递消息,用字符串表示String msg
- 传递集合,用集合表示List list
- 传递对象,用对象表示Object obj
将这些信息封装到一个对象中,这个对象称为返回结果类RestResult对象
RestResult类具体设计
Postman
Postman API Platform | Sign Up for Free
Java技术栈
服务器端
JavaSE(API、OOP)、JavaEE(Servlet、Spring、SpringMVC)
tomcat
前端0
HTML+CSS+JS+JQUERY、Bootstrap、LayUI、EasyUI、VUE
数据库
MySQL、Oracle、Redis
JDBC、SpringJDBC、MyBatis、MyBatisPlus、JPA
工具
IDEA、HBuilder、VSCode、Notepad++、Sublime、Navicat、Postman
JavaEE过程
Servlet + JDBC + JSP(bootstrap) 图书商场
Spring + SpringMVC + SpringJDBC + JSP(bootstrap)
Spring + SpringMVC + MyBatis+ JSP 图书管理系统SSM
SpringBoot + MyBatis + html(LayUI) 酒店客房管理(可前后端分离)
SpringBoot + MyBatisPlus + html(Thymeleaf+bootstrap) 答题系统
SpringBoot + Spring Data JPA
项目一
SpringBoot+MyBatisPlus+Vue 仓库管理(前后端分离)