每天一点
单例设计模式
-
饿汉式
public class ErHan{ Private ErHan(){ } Private static final ErHan erhan = new Erhan(); Public static ErHan getInstance(){ Return erhan; } }
-
饱汉式
提一下反射,反射就是用于破坏实例对象的私有化的,可以强行创建对象。
Public class BaoHan{ Private BaoHan(){ } Private static BaoHan baohan; Public static BaoHan getInstance(){ If(baohan == null){ BaoHan baohan = new BaoHan(); } Return baohan; } }
序列化流
//使用ObjectoutputStream(序列化流)创建ObjectOutputStream对象,构造方法中传递字节输出流
ObjectoutputStream oos = new ObjecroutputStream(new FilleoutputStream(new File(“D:\file.txt”)));
//使用序列化的特有的方法 使用ObjectOutputStream对象中的方法writeObject()方法,把对象写到文件中
Object o = oos.writeObject();
之后不要忘记释放资源
序列化
把对象以流的方式,写入到文件中保存,叫写对象,也叫对象的序列化。对象就是Object,转换成字节,写入字节流就是OutputStream,那我们就使用ObiectOutputStream类把对象序列化。
创建反序列化类并使用
反序列化的前提:
1.类必须实现Serializable接口
2.必须存在对应的class文件
该类需要抛出两个异常:一个 IOException,另一个ClassNotFoundExceptionclass文件找不到异常
反序列化流
//使用ObjectInputStream(序列化流)创建ObjectInputStream对象,构造方法中传递字节输出流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\01全栈\后端\第二阶段源码\javaDay04_BookBorrowingManagement\src\com\sk\io流_03\book.txt")));
//使用反序列化的特有的方法 使用ObjectInputStream对象中的readObject方法把对象从文件中读取出来
Object o = ois.readObject();
之后不要忘记释放资源
图书管理系统
加入io流使用序列化流,以对象的存储
初始化和删除、增加都是将对象从文件中读取出来放在一个集合中,使用集合进行增加和删除,初始化就是给集合初始化,之后写入文件
//使用遍历根据编号查找书籍信息 public Book getBookByBookId(String bookbh) throws IOException, ClassNotFoundException {
//在最上面定义好oos 和ois
ois = new ObjectInputStream(new FileInputStream(new File("D:\\01_全栈\\后端\\第二阶段_源码\\javaDay04_BookBorrowingManagement\\src\\com\\sk\\io流_03\\book.txt")));
//将读出的数据放在一个集合中,进行一下强转 List<Book> book = (List<Book>) ois.readObject(); //System.out.println(book);
//释放资源 ois.close(); //将集合遍历,找到需要的元素 for (Book bookid : book) { if (bookbh.equals(bookid.getBookid())) { //System.out.println(bookid); return bookid; } } return null; }
动态数组的扩容新增删除
扩容
扩容就是将数组元素放到一个更大的数组中
新增元素
public static void add(int index,int[] array,int newadd){ //创建一个新的数组 int [] newArr =new int[array.length+1]; //使用arraycopy进行拷贝 System.arraycopy(array,0,newArr,0,index); System.arraycopy(array,index,newArr,index+1,array.length-index); //最后将新增原酸插在数组中 newArr[index]=newadd; System.out.println(Arrays.toString(newArr)); /*array=newArr; System.out.println(Arrays.toString(array));*/ }
删除
第一种方法:使用循环将旧数组拷贝到新数组中,将要删除的元素不进行拷贝
for(int i = 0;i<newArr.length;i++){ if(i<index){ newArr [i] = array [i]; }else { newArr[i]= array[i+1]; } }
第二种方法:使用System中的arraycopy方法进行拷贝
System.arraycopy(array,0,newArr,0,index); System.arraycopy(array,index+1,newArr,index,array.length-1-index);
arraycopy的五个参数 需要的拷贝数组 ,拷贝数组那个索引开始,复制后的新数组,放在新数组的位置,拷贝元素的个数
Arrays类
其中的Arrays类中有一个toString()方法用于输出数组
我们还可以通过调用系统自带的java.util.Arrays类中的方法对数组进行动态扩容。Arrays类中实现数组扩容的方法为copyof。(需要导入java.util.Arrays)
copyof方法的扩容原理为使用零复制指定的数组,截断或填充(如有必要),以使副本具有指定的长度,调用格式为Arrays.copyof(原数组名,扩容后的数组大小)
public class ArrayTest { public static void main(String[] args) { int[] arr = {1, 3, 4}; //定义一个容量为3的数组arr arr = Arrays.copyOf(arr,7);//将数组arr扩容到7 arr[6] = 5; //给扩容后的数组下标6定义一个值 System.out.println(Arrays.toString(arr));//打印新数组 } }
使用递归计算1-100的和
public class Sum { public static int calcSum(int n) { if (n == 1) { return 1; } else { return n + calcSum(n - 1); } } public static void main(String[] args) { int n = 100; int sum = calcSum(n); System.out.println("1到" + n + "的和为:" + sum); }
自己写的代码
public static void main(String[] args) { Scanner scanner =new Scanner(System.in); System.out.println("请输入一个正整数值"); int n = scanner.nextInt(); function1(n); int num =function1(n); System.out.println(num); } public static int function1(int n){ if(n == 1){ return 1; } return function1(n - 1) + n; }
JDK8为什么把方法区叫做叫元空间呢?
主要原因就是,从JDK8开始使用的是我们本地的物理内存。因为方法区的回收效果不好,那干脆直接使用本地的物理内存。这个本地的物理内存就是元空间。
parseInt(字符串解析成整数)
它可以把一个字符串解析成整数。例如:
let numStr = "42"; let num = parseInt(numStr); console.log(num); // 输出 42
parseInt
还可以指定基数(即要解析的数字的进制),例如:
let numStr = "1010"; let num = parseInt(numStr, 2); // 解析二进制数 console.log(num); // 输出 10
需要注意的是,当解析的字符串无法转换成有效的数字时,parseInt
会返回 NaN
(不是一个数字)。因此,在使用 parseInt
时应该注意错误处理
迭代器
public class Test01 { public static void main(String[] args) { Collection<String > coll= new ArrayList<>(); coll.add("aaa"); coll.add("bbb"); coll.add("ccc"); coll.add("ddd"); Iterator<String> it = coll.iterator(); while (it.hasNext()){ String str = it.next(); //System.out.println(it.next()); if(str.equals("aaa")){ it.remove(); } } System.out.println(coll); } }
Collection集合获取迭代器
Iterator<E> iterator() 返回迭代器对象,默认指向当前集合的0索引
//创建对象 Collection<String > coll= new ArrayList<>();
Iterator中的常用方法
boolean hasNest() 判断当前位置是否有元素,有元素返回true,没有元素返回false
E next() 获取当前位置的元素,并将迭代器对象移向下一个位置
还有一个remove的方法,在迭代器遍历的时候,没办法使用数组的删除,删除元素,但是可以使用iIterator中的remove
Iterator.remove()
方法可以安全地从迭代器指向的基础集合中删除当前迭代的元素,而不会引起并发问题。
使用 remove()
的一般步骤如下:
-
通过获取迭代器的方式,获取要操作的集合的迭代器
Iterator<String> iterator = list.iterator();
-
遍历集合,找到需要删除的元素
while(iterator.hasNext()){ String item = iterator.next(); if(item.equals("hello")){ //根据条件判断要删除的元素 iterator.remove();//使用remove方法从集合中安全地删除当前迭代的元素 } }
在集合中使用 remove()
方法时,需要注意以下几点:
-
不能连续多次调用
remove()
方法,必须在下一次迭代之前调用hasNext()
、next()
或previous()
方法。 -
不能在迭代器调用
remove()
方法之前调用add()
、remove()
或set()
方法。 -
调用
remove()
方法之前必须先调用next()
或previous()
方法,否则会抛出IllegalStateException
异常。
给属性赋值的几种方式
赋值的几种方法 :直接赋值 构造方法 匿名块 内省 第三方JavaBen工具类 getset方法 注解
线程池的回收
线程池中核心线程的回收机制是指对于长时间处于空闲状态的核心线程(即没有任务可执行)的处理机制。一般来说,线程池中的核心线程会在空闲一定的时间后被回收,以减少资源的占用。 具体的回收机制可能因线程池的实现而有所不同,下面是一种常见的线程池核心线程回收机制的简要描述: 1. 空闲时间设置:线程池会设置一个空闲时间参数,即核心线程在空闲一定时间后会被回收。这个时间可以根据具体的需求进行调整。 2. 线程保留:线程池会根据实际情况保留一定数量的核心线程,以应对突发的任务需求。一般会保留一个最小线程数的核心线程,确保线程池的响应性能。 3. 线程回收:当一个核心线程空闲时间超过设定的空闲时间后,线程池会判断是否回收该核心线程。如果需要回收,则会将该线程从线程池中移除,并释放相关的资源。 4. 最大线程数限制:线程池中还会设置一个最大线程数的参数。当线程池的任务需求超过最大线程数时,线程池可以根据策略来增加线程。回收核心线程也可以使得线程池中的线程数量保持在一定的范围内,避免无限增长。 需要注意的是,线程池回收核心线程的机制需要进行权衡,避免过于频繁地回收和创建线程,以提高线程的利用率和系统的性能。合理设置空闲时间参数以及最大线程数参数,可以根据实际情况和性能需求进行调整。此外,回收核心线程需要考虑线程安全性和任务处理的正确性,确保任务的可靠执行。
String类的compareTo()方法用于比较两个字符串的字典顺序。其实现过程如下:
-
首先,该方法会比较两个字符串的长度。如果两个字符串长度相同,则按字符逐个比较;如果长度不同,则比较它们的长度差值。
-
然后,从第一个字符开始逐个比较两个字符串的字符。比较时,会根据字符的Unicode值进行比较。
-
如果当前字符的Unicode值相等,则继续比较下一个字符,直到找到不相等的字符或者到达其中一个字符串的末尾。
-
如果找到不相等的字符,则根据它们的Unicode值的大小关系确定两个字符串的先后顺序。
-
如果到达其中一个字符串的末尾,但另一个字符串还有剩余字符,则较长的字符串会被认为是更大的字符串,即长度较长的字符串大于长度较短的字符串。
-
最后,根据比较结果返回一个整数值。如果第一个字符串小于第二个字符串,则返回一个负数;如果两个字符串相等,则返回0;如果第一个字符串大于第二个字符串,则返回一个正数。
什么是javaBean类?
JavaBean的概念 : 是一种可重复使用、且跨平台的软件组件。JavaBean可分为两种:一种是有用户界面(UI,User Interface)的JavaBean;还有一种是没有用户界面,主要负责处理事务(如数据运算,操纵数据库)的JavaBean
JavaBean的特征 一个标准的JavaBean有以下几个特性:
-
JavaBean是一个公共的(public)类
-
JavaBean有一个不带参数的构造方法
-
JavaBean通过setXXX方法设置属性,通过getXXX方法获取属性
实体类大部分情况下可以使用JavaBean类来实现。实体类通常用于表示业务领域中的对象或数据实体,封装了相关属性和方法。JavaBean类可以作为实体类的一种常见实现方式,以便于对实体对象的属性进行封装和访问。
需要注意的是,并非所有实体类都必须是JavaBean类,尤其是在一些简单的应用场景中,可以直接使用public属性而不提供getter和setter方法。另外,JavaBean类也不仅仅用于实体类,它在其他场景中的应用也非常广泛。
JVM、JRE和JDK的 关系
-
JVM :java虚拟机,真正运行java程序的地方
-
核心类库:java自己写好的程序,给程序员自己的程序调用的
-
JRE:java的运行环境
-
JDK:java开发工具包(包括上面所有)
JVM+核心类库 = JRE
JVM + 核心类库 + 开发工具 = JDK
关于HaspMap的问题?
HasMap的工作原理 ?
HashMap是线程安全的吗 ?
HaspMap不是安全的,怎么解决 ?
他们是怎么解决的?
十进制转为二进制的算法?
-
除二取余法
)V0HJDB81UJ~NW`8{~_N.jpg)
进行除以2,每位的余数与最后没办法的除2的数,从低到高数
二进制转为十进制的算法?
二进制转为十进制就是在二进制的数的每2^X,1101,就是1*2^3 + 1 * 2^2 + 0 * 2^1 + 1*2^0= 13
计算机中表示数据的最小单元
-
计算机中表示数据的最小单元:一个字节(btye ,简称B,是使用8个二进制位组成的)
-
字节中的每个二进制位就称成位(bit,简称b),1B= 8b
多线程的三种创建方式
多线程的三种创建方式
-
继承Threa类
-
实现Runnable类
-
实现Callable类(这个方法可以存在返回值)
第一种 继承Threa类
//建立MyThread继承Thread的类 public class MyThread extends Thread { /*多线程的两种实现方式区别(编程中用哪个比较好? 继承Thread类还是使用Runnable接口?为什么),用代码来演示*/ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName()+"你好"); } } //测试类 /**多线程的第一种启动方法 * 1.自己定义一个类继承的Thread * 2.重写run方法 * 3。创建子类的对象,并启动线程 * */ /**这是第一种的方式,继承Thread类的方式 这个方式的就是创建线程对象*/ MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start();
第二种 实现Runnable类
//建一个MyRun去实现Runnable public class MyRun implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { //获取当前线程的名字 /* Thread thread = Thread.currentThread(); System.out.println(thread+"hello");*/ //练市编程 System.out.println(Thread.currentThread().getName()+"hello"); } } } /** 多线程的第二种启动方法 1.自己定义一个类实现Runnable接口 2.重写里面的run方法 3.创建自己的类的对象 4.创建一个Threa类的对象,并开启线程 */ /**第二种方式*/ //创建一个MyRun的对象 //表示多线程需要执行的任务 MyRun myRun = new MyRun(); //创建线程对象 Thread t3 = new Thread(myRun); Thread t4 = new Thread(myRun); t3.setName("线程3————>"); t4.setName("线程4————>"); //开启线程 t3.start(); t4.start();
第三种 实现Callable类(这个方法可以存在返回值)
public class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { //求1-100之间的和 int sum = 0; for (int i = 0; i <= 100; i++) { sum =sum +i; } return sum; } } /** 多线程的第三种实现方式 特点:可以获取到多线程运行的结果 1.创建一个类MyCallable实现Callable接口 2.重写call(是有返回值,表示多线程运行的结果) 3.创建MyCallable的对象(表示多线程要执行的任务) 4.创建FutureTask的对象(作用管理多线程的结果) 5.创建Thread类的对象,并启动(表示线程) */ /**第三种方式*/ //创建MyCallable对象(表示多线程要执行的任务) MyCallable mc = new MyCallable(); //创建FutureTask的对象(作用管理多线程运行的结果) FutureTask<Integer> ft = new FutureTask<>(mc); //创建线程对象 Thread t5 = new Thread(ft); //启动线程 t5.start(); //获取多线程的运行的结果 Integer result = ft.get(); System.out.println(result);
CRM的学习
-
登录页面
登录页面任务一:将html页面转变为jsp页面
登录页面任务二:使用ajax进行局部刷新
登录页面任务三:将登录错误信息以抛出异常的形式输出到ajax更新部分
知识点补充
-
html页面给为jsp的须知:
-
表头:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
-
根路径设置
<%String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";%>//设置基础地址
不光需要添加上述代码,还要在head头部添加<base href="<%=basePath%>" 在 <meta charset="UTF-8">后面就可以
-
在head的link和script的href或者是src需要路径的地方添加上绝对路径
<link href="<%=request.getContextPath()%>/jquery/bootstrap_3.3.0/css/bootstrap.min.css" type="text/css" rel="stylesheet"/> <script type="text/javascript" src="<%=request.getContextPath()%>/jquery/jquery-1.11.1-min.js"></script> <script type="text/javascript" src="<%=request.getContextPath()%>/jquery/bootstrap_3.3.0/js/bootstrap.min.js"></script>
-
-
ajax:
<script type="text/javascript"> $(function () { if(window.top!=window){//在进去首页后,服务器断开,再次启动会造成登录页面在首页中 //将登录页面不是top顶头的时候,设为顶头 window.top.location=window.location; } //页面加载完毕后,让用户的文本框自动获得焦点 $("#loginAct").focus(); function login() { $.ajax({ type: "POST", url: "login.do",//这里是跳转servlet的 data: "loginAct=" + $("#loginAct").val() + "&loginPwd=" + $("#loginPwd").val(), dataType: "json", success: function (s) { console.log(s) if (s.success) { document.location.href = "workbench/index.jsp" } else { $("#msg").html(s.msg); } } }) } $("#btn").click(function (event) {//绑定from表单 event.preventDefault();//阻止事件的默认行为 login() }) $(document).keydown(function (event) { if (event.keyCode == '13') { login() } }) }) </script>
这是ajax使用jquery封装工具后的代码。比原生ajax更加方便,简洁,$.ajax( )方法有五个参数如下
$.ajax 方法
url 表示请求的地址
type 表示请求的类型 GET 或 POST 请求
data 表示发送给服务器的数据
格式有两种: 一:name=value&name=value 二:{key:value}
success 请求成功,响应的回调函数
dataType 响应的数据类型 常用的数据类型有:
text 表示纯文本
xml 表示 xml 数据
json 表示 json 对象
<script> $.ajax({ url :"login",//路径,因为上面定义了绝对路径,这里可以简写 Type:"GET", //这是发送什么请求 get或者post data:{ "loginAct": loginAct, "loginPwd": loginPwd }, dataType: "json", //json xml text success:function(s){ //function是函数 s是返回的参数 if(s.success){ document.location.href ="workbech/index.jsp" //没有出错跳转那个页面 }else{ $("#msg").html(s.msg) //"#msg"这是绑定按钮,按钮的id ="msg", .html可以将后面的内容在前端输出,这里是抛出异常的形式,将账号密码错误输出 //当然也可以写死 $("#msg").html(<font color='red'>账号密码错误</font>) } } }) </script>
还有其他两种$.get
方法和$.post
方法 和 $.getJSON
方法 ,这次没有用到,写到mind上吧
-
jquery:
jQuery是JavaScript库,*也即jQuery中是封装了很多的javaScript语句,函数*。相当于Java的工具类,其也是一款跨主流浏览器的JavaScript库,简化了JavaScript对HTML,Dom的操作。
jQuery的核心:选择器
$('#id') id选择器 $('.类名') 类名选择器 $('标签名') 标签选择器 $('*') 所有选择器 $('#id,类名,标签名') 组合选择器 $(':input标签中的属性') 表单选择器
jquery绑定事件:
$("#btn").click(function(event){//绑定id=btn的进行点击的事件 event.preventDefault();//阻止事件的默认行为 })
-
session:
//请求req获取session,设置setAttribute共享数据 req.getSession().setAttribute("checking",checking); //获得session User user =(User) session.getAttribute("checking");
-
密码加密:
//将密码的明文形式转换为MD5的密文形式 psw = MD5Util.getMD5(psw);
-
使用ajax异步更新的时候不能在servlet请求转发或者是重定向?
因为ajax异步进行的时候,主线程没有变化,局部更新的线程更新是不行的
解决方式 : 将数据response.getWriter().print(json);提交给前端,在前端进行处理
在前端ajax的success中的function(s){
s参数是后端传给前端的数据,使用documnet.location.href="workbench/index.jsp"的方式进行局部更新
}
前端到数据的库的贯通
后端任务一:在后端获取前端的数据(账号密码)
后端任务二:使用servlet的获取数据库的数据
后端任务三:在service层进行判断账号密码是否正确,并自定义异常,抛出账号密码错误的信息
-
前端到数据库
-
从前端起始点(ajax发起的请求,请求位置在$.ajax({url:"login.do“, ....}))开始,url:"login.do"
的开始,跳转到servlet中,与servlet的注解@WebServlet("/login.do")相对应
这里的url没有 / 原因是上面加了绝对根路径
-
在servlet中重写service方法,进行判断,再封装一个login方法,判断之后调用login方法,并将req和resp当做参数传递到login中
-
在login方法中获取前端数据,使用的req中的getParameter()方法获取前端输入的数据
String loginAct = req.getParameter("loginAct"); String psw = req.getParameter("loginPwd");
-
获取到 代理类的对象
UserService us = (UserService) ServiceFactory.getService(new UserServiceImpl()); 当然也可以在这里new service的对象 UserService userService = new UserService(); userService.checking(loginAct,loginPwd,ip); 去调用UserService的抽象方法 之后返回的User类型的数据到这 try { User checking = us.checking(loginAct,psw,ip);//这是调用checking方法,用User类型接收 System.out.println(checking); req.getSession().setAttribute("checking",checking);//将checking对象的数据存放在Session中 PrintJson.printJsonFlag(resp,true);//调用工具类,将数据传回前端 } catch (LoginException e) { String msg = e.getMessage();//获取异常对象的是错误消息 Map<String,Object> map = new HashMap<>();//数据放在map中 map.put("success",false); map.put("msg",msg); PrintJson.printJsonObj(resp,map);//调交给前端 }
-
在UserService抽象类再到UserServiceImpl实现类,重写父类的方法checking(),使用工具SqlSessionUtil 类创建对象sqlSession,在使用getMapper方法映射dao层的UserMapper.calss,映射出代理
UserMapper userDao = SqlSessionUtil.getSqlSession().getMapper(UserMapper.class); User user = userDao.check(name, psw); 这里进入了UserMapper中 返回User类型的数据,对数据进行判断,判断是否为空,时间是否过去...,要是为空就 throw new LoginException("账号密码错误"); 抛出自定义异常
UserMapper和UserMapper.xml(Mybatis核心映射文件) 两者之间最容易出错的一点是
Mybatis的SQL映射文件
<mappers><!--自动查找该文件下的映射文件--> <package name="com.study.crm.settings.dao"/> </mappers> <typeAliases><!--给目录下文件起别名--> <package name="com.study.crm.settings.domain"/> </typeAliases>
设置这点之后,UserMapper和UserMapper.xml必须在同一层级之下
-
这就到了查询数据库了(mybatis)
public interface UserMapper { User check(@Param("loginAct")String loginAct,@Param("loginPwd")String psw); }
<mapper namespace="com.study.crm.settings.dao.UserMapper">//这里是映射文件目录 <resultMap id="checkMap01" type="user"> <id property="id" column="id"/> <result property="loginAct" column="loginAct"/> <result property="loginPwd" column="loginPwd"/> </resultMap> <select id="check" resultMap="checkMap01"> select * from tbl_user where loginAct =#{loginAct} and loginPwd =#{loginPwd} </select> </mapper>
查询结束返回User类
-
动态SQL
if
<!--List<Emp> getEmpListByCondition(Emp emp);--> <select id="getEmpListByMoreTJ" resultType="Emp"> select * from t_emp where 1=1 <if test="ename != '' and ename != null"> and ename = #{ename} </if> <if test="age != '' and age != null"> and age = #{age} </if> <if test="sex != '' and sex != null"> and sex = #{sex} </if> </select>
where
-
where和if一般结合使用:
-
a>若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
-
b>若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉
-
注意:where标签不能去掉条件最后多余的and
<select id="getEmpListByMoreTJ2" resultType="Emp"> select * from t_emp <where> <if test="ename != '' and ename != null"> ename = #{ename} </if> <if test="age != '' and age != null"> and age = #{age} </if> <if test="sex != '' and sex != null"> and sex = #{sex} </if> </where> </select>
trim
-
trim用于去掉或添加标签中的内容
-
常用属性:
-
prefix:在trim标签中的内容的前面添加某些内容
-
prefixOverrides:在trim标签中的内容的前面去掉某些内容
-
suffix:在trim标签中的内容的后面添加某些内容
-
suffixOverrides:在trim标签中的内容的后面去掉某些内容
<select id="getEmpListByMoreTJ" resultType="Emp"> select * from t_emp <trim prefix="where" suffixOverrides="and"> <if test="ename != '' and ename != null"> ename = #{ename} and </if> <if test="age != '' and age != null"> age = #{age} and </if> <if test="sex != '' and sex != null"> sex = #{sex} </if> </trim> </select>
choose、when、otherwise
-
choose、when、 otherwise相当于if...else if..else
<!--List<Emp> getEmpListByChoose(Emp emp);--> <select id="getEmpListByChoose" resultType="Emp"> select <include refid="empColumns"></include> from t_emp <where> <choose> <when test="ename != '' and ename != null"> ename = #{ename} </when> <when test="age != '' and age != null"> age = #{age} </when> <when test="sex != '' and sex != null"> sex = #{sex} </when> <when test="email != '' and email != null"> email = #{email} </when> </choose> </where> </select>
foreach
<!--int insertMoreEmp(List<Emp> emps);--> <insert id="insertMoreEmp"> insert into t_emp values <foreach collection="emps" item="emp" separator=","> (null,#{emp.ename},#{emp.age},#{emp.sex},#{emp.email},null) </foreach> </insert> <!--int deleteMoreByArray(int[] eids);--> <delete id="deleteMoreByArray"> delete from t_emp where <foreach collection="eids" item="eid" separator="or"> eid = #{eid} </foreach> </delete> <!--int deleteMoreByArray(int[] eids);--> <delete id="deleteMoreByArray"> delete from t_emp where eid in <foreach collection="eids" item="eid" separator="," open="(" close=")"> #{eid} </foreach> </delete>
-
SQL片段
-
sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入
<sql id="empColumns"> eid,ename,age,sex,did </sql> select <include refid="empColumns"></include> from t_emp
-
模糊查询
<!--模糊查--> <resultMap id="fuzzyqueryMap" type="activity"> <id property="id" column="id"/> <result property="owner" column="owner"/> <result property="name" column="name"/> <result property="startDate" column="startDate"/> <result property="endDate" column="endDate"/> <association property="users" javaType="user">//多对一 <result property="id" column="id1"/> <result property="name" column="name1"/> </association> </resultMap> <select id="fuzzyquery" resultMap="fuzzyqueryMap"> select a.id, a.owner, a.name, a.startDate, a.endDate, u.id as id1, u.name as name1 from tbl_activity a join tbl_user u on a.owner= u.id <trim prefix="where" suffixOverrides="and">//trim用于去掉或添加标签中的内容 <if test="name != '' and name !=null">//if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行 a.name like '%${name}%' and </if> <if test="owner != '' and owner != null"> a.owner like '%${owner}%' and </if> <if test="startDate != '' and startDate != null"> a.startDate like '%${startDate}%' and </if> <if test="endDate != '' and endDate != null"> a.endDate like '%${endDate}%' and </if> </trim> order by a.createTime limit #{pageNo} , #{pageSize} //限值,第一数:索引 ,第二数:显示个数 </select>
分页设置
分页插件的设置
-
第一步:设置引入:记住link是href ,而script的引入是src
<link href="jquery/bs_pagination/jquery.bs_pagination.min.css" type="text/css" rel="stylesheet"> <script type="text/javascript" src="jquery/bs_pagination/jquery.bs_pagination.min.js"></script> <script type="text/javascript" src="jquery/bs_pagination/en.js"></script>
-
第二步:先封装一个函数,让发起ajax请求,先设置pageNo和pageSize两个参数,在调用这个两个参数的时候传入这个两个值page(1,3),先默认在第一个索引位置(索引位置的计算pageNo = (pageNo-1)*pageSize ,索引位置减一,乘以每页数,可以到出现),往后显示三个,往后端传入六个数据
/*封装的分页方法和模糊查询*/ function page(pageNo, pageSize) { var name = $.trim($("#name").val()); var owner = $.trim($("#owner").val()); var startTime = $.trim($("#startTime").val()); var endTime = $.trim($("#endTime").val()); $.ajax({ type: "POST", url: "create04.do", dataType: "json", data: { "name": name, "owner": owner, "startTime": startTime, "endTime": endTime, "pageNo": pageNo, "pageSize": pageSize }, success: function (data) { console.log(data.total) var html = '' //alert(data.fuzzyquery.length) for (var i = 0; i < data.fuzzyquery.length; i++) { //console.log(i) html += '<tr class="active">' html += '<td><input type="checkbox"/></td>' html += '<td><a style="text-decoration: none; cursor: pointer;"οnclick="window.location.href=\'detail.html\';">' + data.fuzzyquery[i].name + '</a></td>' html += '<td>' + data.fuzzyquery[i].users.name + '</td>' html += '<td>' + data.fuzzyquery[i].startDate + '</td>' html += '<td>' + data.fuzzyquery[i].endDate + '</td>' html += '</tr>' } $("#tby").html(html) //alert(data.total) //计算总页数 var totalPages = data.total % pageSize == 0 ? data.total / pageSize : parseInt(data.total / pageSize) + 1; //数据处理完毕后,结合分页插件,对前端展现分页信息 $("#activityPage").bs_pagination({ currentPage: pageNo, // 页码 rowsPerPage: pageSize, // 每页显示的记录条数 maxRowsPerPage: 20, // 每页最多显示的记录条数 totalPages: totalPages, // 总页数 totalRows: data.total, // 总记录条数 visiblePageLinks: 3, // 显示几个卡片 showGoToPage: true, showRowsPerPage: true, showRowsInfo: true, showRowsDefaultInfo: true, //该回调函数是在点击分页组件的时候触发的 onChangePage: function (event, data) { page(data.currentPage, data.rowsPerPage); } }); } }) }
-
第三步:在servlet层接收数据,在前端传给的后端的pageNo和pageSize是String,需要强转成int类型,调用service层的方法
String name = req.getParameter("name"); String owner = req.getParameter("owner"); String startDate = req.getParameter("startDate"); String endDate = req.getParameter("endDate"); String pageNo = req.getParameter("pageNo"); int pageNo1 = Integer.parseInt(pageNo); String pageSize = req.getParameter("pageSize"); int pageSize1 = Integer.parseInt(pageSize); int pageNo2 = (pageNo1 - 1) * pageSize1;
-
第四步:调用的fuzzyquery方法,是返回List<Activity>的,到达service的实现类,以映射获取的ActivityMapper调用的数据库的数据,sql语句在上面的模糊查询的那个
在后端返回给前端一个map集合,前端是怎么处理的
-
第五步:返回数据回前端的,在前端的时候,前端不光需要这次查询出来的数据,还需要总页数(这里需要另一个sql),在返回值的时候,两个查询结果怎么处理返回给前端,使用的map集合存储数据
Map<String, Object> map = new HashMap<>(); map.put("fuzzyquery", fuzzyquery); map.put("total", total);
-
第六步:处理数据,在前端处理数据的时候不能使用map的get方法以key值获得,可以使用的data.fuzzyquery和data.total获取值,fuzzyquery是一个集合,可以使用遍历的方式data.fuzzyquery[i].startDate,total只是一个值,直接data.total
数据库吞数据事件
-
数据库吞数据的原因是:两个查询数据一模一样,数据库显示三条数据,在LIst集合遍历出来是两个,其中两个一致的被吞并了(未添加id数据)
-
解决:加入id数据(或者加入与其他行不同的数据)
为全选的复选框绑定事件,触发全选操作
$("#qx").click(function (){//绑定全选按钮 $("input[name='xz']").prop("checked",this.checked)//会将当前元素选中的状态应用到名为’xz’的所有输入元素上 }) $("#tby").on("click",$("input[name = 'xz']"),function () {// $("#qx").prop("checked",$("input[name ='xz']").length==$("input[name='xz']:checked").length) })
时间控件
引入时间控件
<link href="jquery/bootstrap-datetimepicker-master/css/bootstrap-datetimepicker.min.css" type="text/css" rel="stylesheet"/> <script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/js/bootstrap-datetimepicker.js"></script> <script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/locale/bootstrap-datetimepicker.zh-CN.js"></script>
在全局绑定$(function () {}加入以下代码
$(".time").datetimepicker({ minView: "month", language: 'zh-CN', format: 'yyyy-mm-dd', autoclose: true, todayBtn: true, pickerPosition: "top-left" })
在class中加time
传统请求和ajax请求
使用ajax请求,当前窗口/文档不受影响,JavaScript代码可以检查请求的结果,并使用这些结果执行所需的操作(将HTML动态插入页面,解析JSON并将其用于页面逻辑,解析XML等等)。
传统请求
传统的请求都有哪些?
-
直接的在浏览器地址上输入URL
-
点击超链接
-
提交form表单
-
使用JS代码发送请求
-
window.open(url) 可以拼接参数
-
document.location.href =url
-
window.location.href = url
-
提交form表单,给每个标签name属性,便于后端使用request.getParameter("money");获取数据
-
传统请求存在的问题
-
页面全部刷新导致了用户的体验较差
-
传统的请求导致用户的体验有空白期。(用户的体验是不连贯)
传统请求方式,这几种方式,会把原来页面的内容页面清空掉,然后重新加载一个全新的页面,从a页面跳转到全新的页面,可能出现空白期,用户的体验就间断掉了,如果网速较慢的话,就会有空白延迟,给用户体验较差
ajax请求
AJAX概述
-
AJAX不能称为一种技术,它是多种技术的综合产物
-
AJAX可以让浏览器发送一种特殊的请求,这种请求可以是:异步的
-
什么是异步,什么是同步?
-
假设有t1和t2线程,t1和t2线程并发,就是异步
-
假设有t1和t2线程,t2在执行的时候,必须等待t1线程执行到某个位置之后t2才能执行,那么t2在等t1,虽然他们是排队的,排队的就是同步
-
AJAX是可以发送异步请求的。也就是说,在同一个浏览器是页面当中,可以发送的多个ajax请求,这些ajax请求之间不需要等待, 是并发的
-
-
AJAX代码属于WEB前端的JS代码,和后端的java没有关系,后端也可以是c语言,其他语言
-
AJAX应用程序可能使用XML来传输数据,将数据作为纯文本或者JSON文本传输也同样常见
-
AJAX可以更新网页的部分,而不需要重新加载整个页面(页面局部刷新)
-
AJAX可以做到在同一个网页中同时启动多个请求,类似于同一网页启动“多线程”,一个“线程”一个请求。
Thymeleaf模版引擎的使用
这个引擎的使用,其中有一点就是特别的是必须经过后端,才能渲染页面,不能进行直接页面跳转页面
在html中添加上这个使用xmlns:th="Thymeleaf"
<html xmlns:th="http://www.thymeleaf.org">
SpringMvcConfig.java的配置文件中的添加,添加bean,加前缀和后缀
public final static String CHARACTER_ENCODING = "UTF-8"; public final static String TEMPLATE_PREFIX = "/templates/"; public final static String TEMPLATE_SUFFIX = ".html"; public final static Boolean TEMPLATE_CACHEABLE = false; public final static String TEMPLATE_MODE = "HTML5"; public final static Integer TEMPLATE_ORDER = 1; @Bean public SpringResourceTemplateResolver templateResolver() { SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); templateResolver.setPrefix(TEMPLATE_PREFIX); templateResolver.setSuffix(TEMPLATE_SUFFIX); templateResolver.setCacheable(TEMPLATE_CACHEABLE); templateResolver.setCharacterEncoding(CHARACTER_ENCODING); templateResolver.setTemplateMode(TEMPLATE_MODE); templateResolver.setOrder(TEMPLATE_ORDER); return templateResolver; } /** * 模板引擎 * * @return */ @Bean public SpringTemplateEngine springTemplateEngine(SpringResourceTemplateResolver templateResolver) { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver); return templateEngine; } /** * 视图解析器 * * @return */ @Bean public ThymeleafViewResolver viewResolver(SpringTemplateEngine springTemplateEngine) { ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setTemplateEngine(springTemplateEngine); viewResolver.setCharacterEncoding(CHARACTER_ENCODING); return viewResolver; }
如果是是需要跳转页面,不需要后端的数据处理,在SpringMvcSupport中配置
@Override protected void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("login"); registry.addViewController("login").setViewName("login"); registry.addViewController("regist").setViewName("regist"); registry.addViewController("addEmp").setViewName("addEmp"); }
SpringMvcSupport配置文件的放行
@Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); registry.addResourceHandler("/static/css/**").addResourceLocations("/static/css/"); registry.addResourceHandler("/static/img/**").addResourceLocations("/"); registry.addResourceHandler("/img/**").addResourceLocations( "file:E:/file/img/"); }
使用的thymeleaf进行一个更新的操作
基本的环境配置E:\01_全栈\后端\Code_Student\thymeleaf01,这是地址
从前端开始,html后面需要thymeleaf的显示标签 xmlns:th="Thymeleaf",使用thymeleaf来进行回显
from表单的提交,提交到后端
<form th:action="@{/updateEmpTage02}" method="post" enctype="multipart/form-data">
这是个人数据的回显
编号 <input type="hidden" class="inputgri" name="id" th:value="${employee.id}"> 姓名 <input type="text" class="inputgri" name="name" th:value="${employee.name}"/> 当前的照片显示,再加一行代码,用隐藏域来存储,储存照片的名 <img name="photo" th:src="${#servletContext.getContextPath()} +'/img/'+ ${employee.photo}" width="60" > <input type= "hidden " name = "photo" th:value ="${employee.photo}" > 工资 <input type="text" class="inputgri" name="salary" th:value="${employee.salary}"/> 生日 <input type="text" class="inputgri" name="birthday" th:value="${employee.birthday}"/>
这里的信息的是,点击更新的时候,在后端查询出来的,跳转后端的时候,将id传递过去
<a th:href="@{/deleteEep(id=${emp.id})}">删除</a> <a th:href="@{/updateEmp(id=${emp.id})}">更新</a>
点击更新之后,发起请求,获取这个员工信息,回显到页面
@RequestMapping("/updateEmp") public String updateEmp(Integer id,Model model){ Employee selectemp = employeeService.selectemp(id); System.out.println(selectemp); model.addAttribute("employee",selectemp); return "updateEmp"; }
在更新页面上点击更新
点击更新,进入更新的第二步,进行更新,如果更新照片的话,我们需要判断
@RequestMapping("/updateEmpTage02") public String updateEmp02(Employee employee,MultipartFile img) throws IOException { System.out.println("我进来了"); System.out.println(employee); if (!img.isEmpty()) { String oldphoto = employeeService.selectemp(employee.getId()).getPhoto(); System.out.println("oldphoto = " + oldphoto); //需要找到这对象 File file = new File(realPath, oldphoto); if (file.isFile()) { file.delete(); } //获取当前照片的名字 String filename = img.getOriginalFilename(); //获取当前时间,并将时间以字符串的形式输出 String fileNamePrefix = new SimpleDateFormat("hhhhMMddHHmmss").format(new Date()); //获取照片的最后的后缀,就是截取从后截取到点的地方 System.out.println(filename); String fileNameSuffix = filename.substring(filename.lastIndexOf(".")); //将上面的时间与照片的后缀拼接起来,形成新的名字 String newfile = fileNamePrefix + fileNameSuffix; //这是一个方法,就是将照片复制到指定位置,第一个参数为指定的位置,第二个参数是照片的名字 img.transferTo(new File(realPath, newfile)); employee.setPhoto(newfile); System.out.println("employee = " + employee); Integer integer = employeeService.updateEmp(employee); }else { Integer integer = employeeService.updateEmp(employee); } return "redirect:/emplist2"; }
前端的整个代码不在这放了,直接去找idea中的代码吧E:\01_全栈\后端\Code_Student\thymeleaf01
图形验证码的生成
这是一个工具类,可以生产验证码
package com.sj.utils; import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import java.util.Random; /** *@描述 验证码生成 */ public class VerifyCodeUtils { //使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符 public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; private static Random random = new Random(); /** * 使用系统默认字符源生成验证码 * @param verifySize 验证码长度 * @return */ public static String generateVerifyCode(int verifySize){ return generateVerifyCode(verifySize, VERIFY_CODES); } /** * 使用指定源生成验证码 * @param verifySize 验证码长度 * @param sources 验证码字符源 * @return */ public static String generateVerifyCode(int verifySize, String sources){ if(sources == null || sources.length() == 0){ sources = VERIFY_CODES; } int codesLen = sources.length(); Random rand = new Random(System.currentTimeMillis()); StringBuilder verifyCode = new StringBuilder(verifySize); for(int i = 0; i < verifySize; i++){ verifyCode.append(sources.charAt(rand.nextInt(codesLen-1))); } return verifyCode.toString(); } /** * 生成随机验证码文件,并返回验证码值 * @param w * @param h * @param outputFile * @param verifySize * @return * @throws IOException */ public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException{ String verifyCode = generateVerifyCode(verifySize); outputImage(w, h, outputFile, verifyCode); return verifyCode; } /** * 输出随机验证码图片流,并返回验证码值 * @param w * @param h * @param os * @param verifySize * @return * @throws IOException */ public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException{ String verifyCode = generateVerifyCode(verifySize); outputImage(w, h, os, verifyCode); return verifyCode; } /** * 生成指定验证码图像文件 * @param w * @param h * @param outputFile * @param code * @throws IOException */ public static void outputImage(int w, int h, File outputFile, String code) throws IOException{ if(outputFile == null){ return; } File dir = outputFile.getParentFile(); if(!dir.exists()){ dir.mkdirs(); } try{ outputFile.createNewFile(); FileOutputStream fos = new FileOutputStream(outputFile); outputImage(w, h, fos, code); fos.close(); } catch(IOException e){ throw e; } } /** * 输出指定验证码图片流 * @param w * @param h * @param os * @param code * @throws IOException */ public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{ int verifySize = code.length(); BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); Random rand = new Random(); Graphics2D g2 = image.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); Color[] colors = new Color[5]; Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN, Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.YELLOW }; float[] fractions = new float[colors.length]; for(int i = 0; i < colors.length; i++){ colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)]; fractions[i] = rand.nextFloat(); } Arrays.sort(fractions); g2.setColor(Color.GRAY);// 设置边框色 g2.fillRect(0, 0, w, h); Color c = getRandColor(200, 250); g2.setColor(c);// 设置背景色 g2.fillRect(0, 2, w, h-4); //绘制干扰线 Random random = new Random(); g2.setColor(getRandColor(160, 200));// 设置线条的颜色 for (int i = 0; i < 20; i++) { int x = random.nextInt(w - 1); int y = random.nextInt(h - 1); int xl = random.nextInt(6) + 1; int yl = random.nextInt(12) + 1; g2.drawLine(x, y, x + xl + 40, y + yl + 20); } // 添加噪点 float yawpRate = 0.05f;// 噪声率 int area = (int) (yawpRate * w * h); for (int i = 0; i < area; i++) { int x = random.nextInt(w); int y = random.nextInt(h); int rgb = getRandomIntColor(); image.setRGB(x, y, rgb); } shear(g2, w, h, c);// 使图片扭曲 g2.setColor(getRandColor(100, 160)); int fontSize = h-4; Font font = new Font("Algerian", Font.ITALIC, fontSize); g2.setFont(font); char[] chars = code.toCharArray(); for(int i = 0; i < verifySize; i++){ AffineTransform affine = new AffineTransform(); affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2); g2.setTransform(affine); g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10); } g2.dispose(); ImageIO.write(image, "jpg", os); } private static Color getRandColor(int fc, int bc) { if (fc > 255) fc = 255; if (bc > 255) bc = 255; int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); } private static int getRandomIntColor() { int[] rgb = getRandomRgb(); int color = 0; for (int c : rgb) { color = color << 8; color = color | c; } return color; } private static int[] getRandomRgb() { int[] rgb = new int[3]; for (int i = 0; i < 3; i++) { rgb[i] = random.nextInt(255); } return rgb; } private static void shear(Graphics g, int w1, int h1, Color color) { shearX(g, w1, h1, color); shearY(g, w1, h1, color); } private static void shearX(Graphics g, int w1, int h1, Color color) { int period = random.nextInt(2); boolean borderGap = true; int frames = 1; int phase = random.nextInt(2); for (int i = 0; i < h1; i++) { double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames); g.copyArea(0, i, w1, 1, (int) d, 0); if (borderGap) { g.setColor(color); g.drawLine((int) d, i, 0, i); g.drawLine((int) d + w1, i, w1, i); } } } private static void shearY(Graphics g, int w1, int h1, Color color) { int period = random.nextInt(40) + 10; // 50; boolean borderGap = true; int frames = 20; int phase = 7; for (int i = 0; i < w1; i++) { double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames); g.copyArea(i, 0, 1, h1, 0, (int) d); if (borderGap) { g.setColor(color); g.drawLine(i, (int) d, i, 0); g.drawLine(i, (int) d + h1, i, h1); } } } }
在代码中使用的
@RequestMapping("/image") public void imageView(HttpSession session , HttpServletResponse response){ //生成4位的验证码 String code = VerifyCodeUtils.generateVerifyCode(4); //将验证吗存在session域中 session.setAttribute("code", code); System.out.println(code); //返回给回去 response.setContentType("image/png"); try { //response.getOutputStream():这是获取 Servlet 的响应输出流的方法。它可以用于将数据发送给客户端。 ServletOutputStream outputStream = response.getOutputStream(); //这是一个自定义的工具方法,用于生成指定大小的验证码图片。它接受四个参数:验证码图片的宽度和高度(这里是60和30), // 输出流(即要写入的目标流),以及验证码文本(即要绘制在图片上的验证码内容)。 VerifyCodeUtils.outputImage(60, 30, outputStream, code); //outputStream:这个变量是用来持有输出流的引用,将其作为参数传递给 VerifyCodeUtils.outputImage()方法。这样,生成的验证码图片数据就会被写入到输出流中。 } catch (IOException e) { throw new RuntimeException(e); } }
前端的显示
<tr> <td valign="middle" align="right"> 验证码: <img id="num" th:src="@{/image}" /> //使用id=不同时间戳来更换不同的图片 <a href="javascript:;" οnclick="document.getElementById('num').src = 'image?id='+(new Date()).getTime()">换一张</a> </td> <td valign="middle" align="left"> <input type="text" class="inputgri" name="number" /> </td> </tr>
上传图片并保存数据库
保存图片的到数据库,保存的不是照片的本身,而是照片的位置
pom.xml的依赖加入
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency>
加入依赖之后,需要在from表单之后加上enctype="multipart/form-data",这是必须的,其意思是指表单数据有多部分构成,既有文本数据,又有文件等二进制数据
<form th:action="@{/insertemp}" method="post" enctype="multipart/form-data">
SpringMvcConfig.java的配置文件中的添加,添加bean
/*多文件上传的bean*/ @Bean public CommonsMultipartResolver multipartResolver(){ CommonsMultipartResolver resolver = new CommonsMultipartResolver(); resolver.setMaxUploadSize(1024*1024*20); resolver.setDefaultEncoding("utf-8"); return resolver; }
点击提交,发送传统的提交的,将其的from表达传递过来,因为上述的from加上的标签,可以将照片传递给我后端,后端进行接收,并将数据copy的指定的位置,并以时间戳来命名,再去照片的后缀的,与之拼接的起来,将起这个名字的存在数据库中,后续回显的时候,直接去找我们复制的位置,在数据库中的名字
使用MultipartFile img的方式将照片的参数传递过来
@Autowired //依靠自动注入的Service private UserService userService; @Autowired private EmployeeService employeeService; @Value("${photo.path}") //这是我们将照片的复制到位置,使用photos.properties的方式,方便修改 private String realPath; @RequestMapping("/insertemp") public String addEmp(Employee employee, Model model, MultipartFile img) throws IOException { System.out.println("employee = " + employee); System.out.println("img" + img); //获取当前照片的名字 String filename = img.getOriginalFilename(); //获取当前时间,并将时间以字符串的形式输出 String fileNamePrefix = new SimpleDateFormat("hhhhMMddHHmmss").format(new Date()); //获取照片的最后的后缀,就是截取从后截取到点的地方 String fileNameSuffix = filename.substring(filename.lastIndexOf(".")); //将上面的时间与照片的后缀拼接起来,形成新的名字 String newfile = fileNamePrefix + fileNameSuffix; //这是一个方法,就是将照片复制到指定位置,第一个参数为指定的位置,第二个参数是照片的名字 img.transferTo(new File(realPath, newfile)); employee.setPhoto(newfile); Integer insertemp = employeeService.insertemp(employee); System.out.println(insertemp + "222"); if (insertemp != 0) { System.out.println("完成"); List<Employee> employees = employeeService.employeeList(); model.addAttribute("emplist", employees); return "emplist"; } else { return "addEmp"; } }
photos.properties配置文件
photo.path = E:/file/img
sql的语句
@Insert("insert employee values (null,#{name},#{salary},#{birthday},#{photo})") Integer insertemp(Employee employee);
显示照片的时候,直接在数据库中拿取拼接好的图片名字,设置好路径,直接去该路径下去取
前端发送一个请求
<td> <img th:src="${#servletContext.getContextPath()} +'/img/'+ ${emp.photo}" width="60"> </td>
SpringMvcSupport.java在配置文件中,将其放松,最后那一行是放行图片的,其他的事放行静态资源的,静态资源是我们不需要操控的,比如是css等一个基本的文件,我们需要控制的是页面发起的请求和页面跳转页面,因为thymeleaf模版引擎必须经过后端再返回
@Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); registry.addResourceHandler("/static/css/**").addResourceLocations("/static/css/"); registry.addResourceHandler("/static/img/**").addResourceLocations("/"); registry.addResourceHandler("/img/**").addResourceLocations( "file:E:/file/img/"); }
返回上一个页面onclick
οnclick="window.history.back()"
<input type="button" class="button" value="返回列表"οnclick="window.history.back()" />
增删改查四种SQL语句
增
@Insert("insert user values (null,#{username},#{relaname},#{password},#{gender})") Integer SaveUser(User user);
删
@Delete("delete from employee where id = #{id}") Integer deleteEmp(@Param("id") Integer id);
改
@Update("update employee set name=#{name},salary=#{salary},birthday=#{birthday},photo=#{photo} where id=#{id}") Integer updateEmp(Employee employee);
查