学生图片信息的存储

一·图片的存储的两种解决方案:

1.保存到服务器文件夹,数据库中保存图片路径。

比较适合图片不是非常重要的情况,比如在论坛上发几张图。就没有必要把图存到数据库里,只要把图片存到服务器的文件夹,进行命名,再把这个名字存在数据库的对应字段。

2.保存到数据库的blob字段上

这个是真的要把图片保存到数据库的对应字段上,比如说:身份证的照片。
保存在文件夹,数据库备份的时候是没法备份的,图片没法跟着走

blob: binary large object 在mysql中4G
(tinyblob 255字节, smallblob 64k,mediumblob (16M)… …)
做word或者图片上传还是倾向使用mediumblob

clob: character large object(字符型大对象)
数据库相关的字符串字段是varchar (8000),它最多只能到8000个字符,就不能用来保存红楼梦之类的。这时使用clob,有些数据库叫text。

二·

1·修改Student

给domain增加一个属性,用以保存学生照片

照片以字节数组来存储,在domain下Student添加,属性“stuPic”:

private byte[] stuPic;//学生照片

在这里插入图片描述
添加Getter和Setter:
在这里插入图片描述

2·修改数据库表结构

复制原来的docs到stuinfo下:
在这里插入图片描述

修改内容为:

create table tbl_student(
  stu_no int primary key,
  stu_name varchar(30) not null,
  stu_age decimal(5,2) not null
);

alter table tbl_student add stu_pic mediumblob;

数据库已经有数据了,只能以修改的方式来增加一个图片存储字段。修改数据库表结构用alter,修改数据用update。允许空,原来是有记录的。

登入进数据库,进行修改:
在这里插入图片描述
查看结果,为了不删掉数据,允许空,不提供照片也可以:
在这里插入图片描述

3·修改网页模态窗与设置默认图片

在创建学生时如何发送图片信息呢?

现在我们只有学生学号,学生姓名和学生成绩:
在这里插入图片描述
在其中添加:

<div class="form-group">
	<label>学生照片</label>
	 <input type="file"	class="form-control-file" name="stuPic">
</div>

在这里插入图片描述
效果:
在这里插入图片描述
点击“选择文件”按钮出现选择文件的窗口。是因为我们有一个类型是file。
找一张默认照片:
在这里插入图片描述
随便找一张:
在这里插入图片描述
当用户没有提供照片时就显示这张照片。

在resources下新建文件夹“pics”:
在这里插入图片描述
把图片命名为“default.png”贴到pics下:
在这里插入图片描述
访问“http://localhost:8080/stuinfo/resources/pics/default.png”就可以看到这张图了:
在这里插入图片描述
这张图的分辨率是390x388。
在学生照片的div上添加:

<img src="/stuinfo/resources/pics/default.png" width="150px" height="150px">

效果:
在这里插入图片描述
但是选择文件没办法产生预览,只会这样子显示:
在这里插入图片描述

在list_student.jsp的script处添加:

// 传进来的部件就是文件域
function previewImage(file){
		//获得这个图片
        var img = document.getElementById('picImg');
		//首先要判断文件域有没有文件,如果有文件,就说明你选择里一个文案要上传了。确认file有文件,且第一个文件不为空,做相关的事情。就开始其读数据			
		if (file.files && file.files[0]) 			
		{		
		   //1·准备数据读取器
		   //准备一个文件读取器对象,并告诉它文件读取完毕之后要做什么。
		   var reader = new FileReader();			  
		   //成功读取了图片信息后,把读取结果赋予 	
		   
		   //2·告诉数据读取器,读完之后要做什么
		  //FileReader.readAsDataURL()
          //开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容。			   		
		   reader.onload = function(evt){//onload告诉他文件读取完毕后干什么,“=”后面是一个回调函数
		    img.src= evt.target.result;//当读取完毕之后,把读到的内容赋给src,放到img身上来,就是赋予这个输入控件。就是把这个输入控件读到的数据往img上赋值。从而将img原来的src的值替代掉。实现了预览效果。
		    console.log("read ok! img src get value!"+evt.target.result);//打印出read ok
		   }
			
			//3·开始去读
		   console.log("start to read!")
		   reader.readAsDataURL(file.files[0]);
		
		}
	    else{
	    	//如果没有内容的话就用默认值
	       img.src="/stuinfo/resources/pics/default.png";
	      // alert("no upload file!");
	    }
 		
     }

这个脚本的目标是预览图片:
var img = document.getElementById('picImg');获得这个图片
给img一个id:
修改img为:<img id="picImg" src="/stuinfo/resources/pics/default.png" width="150px" height="150px"/>
在这里插入图片描述

2·修改input,添加onchange="previewImage(this)"

<input type="file"	class="form-control-file" name="stuPic" onchange="previewImage(this)">

“previewImage(this)”是将当前这个输入控件当做参数传到函数里面去
在这里插入图片描述
测试:
在这里插入图片描述
在这里插入图片描述
查看控制台:
在这里插入图片描述
可以看到打印出了一堆图片的信息,以64进制来显示的2进制,这样非常长的2进制显示出来会比较小。

不想选了,点击“选择文件”按钮,到选择文件界面,直接点击取消。就会到了原来的默认图片。

4·数据落地

要使数据落地还需要两个库,fileupload和io:
在这里插入图片描述
在pom.xml中添加:

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

fileupload会把io带进来。他们是层级依赖:
在这里插入图片描述
青色框的部分是会自动封装进去的,但是红色框的呢?
在这里插入图片描述
准备一个多段文件用来接收多段提交的东西,

多段提交:如果要提交的网页中,是有包含文件上传域的,那么编码模式必须设成多段提交。

因为要保存的数据体积是很大的,他必须分成多段来慢慢送。
在list_student.jsp的新生注册模态窗如下位置添加enctype="multipart/form-data"

在这里插入图片描述

来到控制器:
在“createStudent”方法的参数上加上MultipartFile stuPic
在这里插入图片描述
在方法内加一句stu.setStuPic(stuPic.getBytes());
在这里插入图片描述
就可以了。
那么对于表单上传上来的文件,我们准备好一个多段文件,文件名和网页上的name是一致的:
在这里插入图片描述
它就会自动的传上去之后,在“createStudent”方法中作为参数被解析出来。把字节都取出来,填充的字段上。接下来是保存。

保存
保存时是Dao层,Dao层的实现类是没有的,要去修改映射文件
来到StudentMapper,修改addStu:

   <insert id="addStu" parameterType="Student">
      insert into tbl_student 
      values(#{stuNo},#{stuName},#{stuMark},#{stuPic})
   </insert>

我们是按照顺序写的,所以没有必要写出字段名。到时候会从Student中取到stuPic送到对应字段上。
字节数组映射到blob字段是Mybatis所支持的。要不然要对框架进行定制,根据框架提供的插件开发机制,使用接口,在框架上再配置一下。
测试:
来到控制器设个断点:
在这里插入图片描述
在这里插入图片描述
点击“新生注册”
在这里插入图片描述

在这里插入图片描述
他还是空。

现在,我们的表单已经设成了多段,控制器中也准备了多段文件。
那么,表单中的file类型的输入控件,怎么样才能变成学生控制器中的多段文件呢?要提供一个部件,让springmvc知道怎么处理多段提交的文件。

在dispatcher-servlet.xml添加:

<!-- 遇到多段提交数据,一律使用这个解析器去处理,其会调用commons-fileupload去解决问题 -->
<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<!--上传文件的最大大小,单位为字节 --> 
	<property name="maxUploadSize" value="17367648787"></property>		 
	<!-- 上传文件的编码 -->
	<property name="defaultEncoding" value="UTF-8"></property>
</bean> 

必须要加上多段解析器,否则springmvc将无法对多段数据进行处理。定义了一个springmvc所用的bean,只要是多段提交,这个bean都将被启动。

因为学生的属性是“stuPic”和网页的name的“stuPic”是同名的,他优先往学生的字段里面送。系统就不写了。
将网页的name的“stuPic”更名为“stuPhoto”,同时,将控制器中的多段文件名也改为“stuPhoto”

测试:
在这里插入图片描述

在debug窗口可以看到:
在这里插入图片描述
在网页可以看到:
在这里插入图片描述

图片并没有在网页列表中显示,在mysql中可以看到:
在这里插入图片描述

后面的字段是图片。

接下来是图片怎么在网页显示出来

将图片放在姓名上面,
修改list_student.jsp:
原来:
在这里插入图片描述
现在:
在这里插入图片描述

效果:
在这里插入图片描述
调小一点:

在这里插入图片描述
在这里插入图片描述

现在暂时都用默认图片,那么怎么把图片从数据库取出来呢?
到Dao层进行一定处理
在StudentDao添加:

byte[] getPicByStuNo(int stuNo);

接下来就是写映射文件
在StudentMapper.xml添加:

   <select id="getPicByStuNo" parameterType="int" resultType="byte[]">
      select stu_pic
      from tbl_student
      where stu_no=#{stuNo}
   </select> 

传进来的是整型参数,传出去都是字节数组,
在Service层进行修改:
在StudentService添加:

byte[] loadStuPhoto(int stuNo);

在StudentServiceImpl实现一下:

@Override
public byte[] loadStuPhoto(int stuNo) {
	return stuDao.getPicByStuNo(stuNo);
}

回到springmvc,回到控制器:
要加载某个学生的照片,在专门写一个方法,需要HTTPResponse来做这个事情,在参数上写就好:

 @GetMapping("/students/{stuNo}/photo")
 public String loadStuPic(@PathVariable int stuNo,HttpServletResponse response) throws Exception{
	 
	 byte[] data = stuService.loadStuPhoto(stuNo);
	 
	 //MIME类型,在正式发送之前,要告诉他我发送的是什么东西。MIME是网页上的规范化文件识别机制,前面是大类型(image),后面是小类型(jpeg)。然后他就做好准备了,准备接收二进制数据,接收完二进制之后就以图片的方式去理解,就显示出图片了。
	 response.setContentType("image/jpeg");
	 ServletOutputStream sos=response.getOutputStream();
	 sos.write(data);
	 sos.flush();
	 sos.close();
	 
	 return null;
 }

HTTPResponse是通向浏览器的一个通道。

如果是这样的路径就会取到这个学生的照片

测试:
访问:http://localhost:8080/stuinfo/students/110/photo
在这里插入图片描述

来到StudentMapper,添加resultMap:
修改,并添加:
在这里插入图片描述
创建了一个resultMap,这个resultMap返回回来的类型是一个Map,整个resultMap的ID叫picMap。
要查的是“stu_pic”字段,用这个org.apache.ibatis.type.BlobTypeHandler转化器去转,转完之后以imgBytes为key存到java.util.Map里面去。
大致为:

     <select id="getPicByStuNo" parameterType="int" resultMap="picMap">
      select stu_pic
      from tbl_student
      where stu_no=#{stuNo}
   </select> 

   <!-- 
     mediumblob -> byte[]   利用BlobTypeHandler,自定义转换器
     
     type="java.util.Map"  返回的类型是key/value键值对存储的map
     column="stu_pic" 取的字段
     property="imgBytes"  map中的key
     
    -->
   <resultMap type="java.util.Map" id="picMap">
        <id column="stu_pic" property="imgBytes" typeHandler="org.apache.ibatis.type.BlobTypeHandler"/>
   </resultMap> 

修改StudentDao:
在这里插入图片描述
修改StudentServiceImpl:
在这里插入图片描述

@Override
public byte[] loadStuPhoto(int stuNo) {
	return (byte[])stuDao.getPicByStuNo(stuNo).get("imgBytes");
}

测试:
在这里插入图片描述
要将mediumblob转成byte[] 但是不能自动转 ,所以利用Mybatis提供的类型转换器 BlobTypeHandler,如果以后的转化Mybatis没有提供就要自定义转换器。就是“MyBatis自定义类型处理器 TypeHandler”

 type="java.util.Map"  返回的类型是key/value键值对存储的map
 column="stu_pic" 取的字段 
 property="imgBytes"  map中的key
 在插入时可以直接在旁边写上类型转换器:

在这里插入图片描述

在这里插入图片描述

结果转换就不行了,要写resultMap了。

我们原来的返回值是一个我们自定的类型(Student),但是返回一张照片的话是什么类型?什么类型都不是,所以使用resultMap后返回值是(picMap),是key/value键值对存储的map:
在这里插入图片描述
现在还要考虑没有图的情况
在StudentServiceImpl中修改:
在这里插入图片描述

@Override
public byte[] loadStuPhoto(int stuNo) {
	Map picMap = stuDao.getPicByStuNo(stuNo);
	if(picMap!=null)
		return (byte[])picMap.get("imgBytes");
	else {
		return null;
	}
	
}

在控制器中修改:
在这里插入图片描述

 @GetMapping("/students/{stuNo}/photo")
 public String loadStuPic(@PathVariable int stuNo,HttpServletResponse response) throws Exception{
	 //这样就得到了数据了
	 byte[] data = stuService.loadStuPhoto(stuNo);
	 //数据怎么带回去是一个问题
	 if(data!=null) {
	 //给response设置一下返回什么东西
	 response.setContentType("image/jpeg");
	 //从response身上得到一个输出流
	 ServletOutputStream sos = response.getOutputStream();
	 sos.write(data);
	 sos.flush();
	 sos.close();
	 }else {
		 System.out.println("no image");
	 }
	 //上面已经把数据输出去了,就没有必要return了
	 return null;
	 
 }

在list_student中修改:
在list的位置:

在这里插入图片描述

<img src="/stuinfo/students/${stu.stuNo}/photo" width="100px" height="100px"/>

拼装路径。
效果:
在这里插入图片描述
新增:
在这里插入图片描述
在这里插入图片描述
那么 ,没有图的怎么办呢?
在没有图片时,采取的方法是使用默认图片:
对控制器部分修改:

	 @GetMapping("/students/{stuNo}/photo")
	 public String loadStuPic(@PathVariable int stuNo,HttpServletRequest request,HttpServletResponse response) throws Exception{
		 //这样就得到了数据了
		 byte[] data = stuService.loadStuPhoto(stuNo);
		 
		 if(data==null || data.length==0){//去取数据,发现什么都没取到,说明是没东西的。将默认图片替换进去。下面再去读是data是一定有东西的。		
				String defaultPicPath = request.getRealPath("/")+"resources/pics/default.png"; //获取网址“pics/default.png”,真实磁盘路径。“request.getRealPath("/")”是获取当前web应用程序所对应的根路径所对应的物理路径
			    FileInputStream fis =new FileInputStream(defaultPicPath);//创建一个文件输入流,把文件读取出来
				data=new byte[fis.available()];//根据文件体积大小,创建字节数组
				fis.read(data);//从fis中读出数据存到data中来,data就拥有了默认图片的数据
				fis.close();
		 }

		 //数据怎么带回去是一个问题
		 //给response设置一下返回什么东西
		 response.setContentType("image/jpeg");
		 //从response身上得到一个输出流
		 ServletOutputStream sos = response.getOutputStream();
		 sos.write(data);
		 sos.flush();
		 sos.close();

		 //上面已经把数据输出去了,就没有必要return了
		 return null;
		 
	 }

data的数据获取方式只有两个,无论怎样都有数据:
在这里插入图片描述
效果:
在这里插入图片描述

第二种方法是在网页端判断是否有图片。

在修改学生时,显示图片:
在update_student添加:

  <div class="form-group">
    <label>学生照片:</label><br>
    <img class="my-3" src="/stuinfo/students/${stu.stuNo}/photo" width="100px" height="100px"/>
    <input type="file" class="form-control-file" name="stuPhoto"><!-- 用EL表达式来回填的 -->
  </div>

在这里插入图片描述

测试:
在这里插入图片描述
增加修改完图片的预览:
在update_student开头添加:
在这里插入图片描述

function previewImage(file){

	//获得这个图片
    var img = document.getElementById('picImg');
	//确认file有文件,且第一个文件不为空,做相关的事情	
	if (file.files && file.files[0]) 			
	{		
	   
	   //准备一个文件读取器对象,并告诉它文件读取完毕之后要做什么。
	   var reader = new FileReader();			  
	   //成功读取了图片信息后,把读取结果赋予 	
	   
	  //FileReader.readAsDataURL()
      //开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容。			   		
	   reader.onload = function(evt){
	    img.src= evt.target.result;
	    console.log("read ok! img src get value!"+evt.target.result);
	   }
	
	   console.log("start to read!")
	   reader.readAsDataURL(file.files[0]);
	
	}
    else{
       img.src="/stuinfo/resources/pics/default.png";
      // alert("no upload file!");
    }
		
 }

修改图片显示,大致如下:

  <div class="form-group">
    <label>学生照片:</label><br>
    <img id="picImg" class="my-3" src="/stuinfo/students/${stu.stuNo}/photo" width="100px" height="100px"/>
    <input type="file" class="form-control-file" name="stuPhoto" onchange="previewImage(this)"><!-- 用EL表达式来回填的 -->
  </div>

在这里插入图片描述

测试:
在这里插入图片描述
在这里插入图片描述

现在只是预览,还没有上传上去,接下来,实现上传:
在这里插入图片描述
PUT提交不支持,要加上多段,没有多段解析器处理不来:
Mapper那边存的时候是自动处理的。

修改update_student:

enctype="multipart/form-data"

在这里插入图片描述
修改控制器:

@PutMapping("/students/{stuNo}")
public String updateStudent(Student stu, @PathVariable int stuNo,MultipartFile stuPhoto) throws Exception{
	if(stuPhoto!=null)
		stu.setStuPic(stuPhoto.getBytes());
	stuService.updateStudent(stu);
	
	return "redirect:/students";
	
}

测试:
在这里插入图片描述

在多个页面都要用到预览的脚本,那就将其做成外挂的模式:
在静态资源文件夹resources下,新建js文件夹,在其下新建common.js,将重复的脚本复制进来:

在这里插入图片描述
在list_student.jsp添加:

<script type="text/javascript"
src="/stuinfo/resources/js/common.js"></script>

在这里插入图片描述
来到修改界面:
添加:
在这里插入图片描述
删除原来的脚本。

测试:

在这里插入图片描述
点的到:

在这里插入图片描述
测试预览OK。

在多段提交时,过滤器没有办法把post提交转换成put提交。
现在把update_student.jsp的put提交拿掉:
在这里插入图片描述
回到学生控制器:
将修改的PUT提交给为post提交:
在这里插入图片描述
检查没有发现提交方式与网址和这个方法相同的情况。没有冲突。
测试填充数据能不能修改:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
改了。
但是现在如果只是修改名字,照片不改:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
原因:
如果没有选择照片,就是空。
来到控制器:
在这里插入图片描述

修改判断条件,判断是有东西的我才往里面塞。
否则就是用户并没有去修改照片,认为照片没有问题,直接用数据库里的照片就好了。再把数据库的照片取出来填上。最理想的方式是
:在修改时没有更新图片时,sql的:
在这里插入图片描述
这个部分就不生成。就不会修改照片了。这就是动态sql语句。

@PostMapping("/students/{stuNo}")
public String updateStudent(Student stu, @PathVariable int stuNo,MultipartFile stuPhoto) throws Exception{
	if(stuPhoto.getBytes()!=null&&stuPhoto.getBytes().length>0)
		stu.setStuPic(stuPhoto.getBytes());
	else {
		stu.setStuPic(stuService.loadStuPhoto(stuNo));
	}
	stuService.updateStudent(stu);
	
	return "redirect:/students";
	
}

测试没有修改的情况下是OK的。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值