课前介绍
课程介绍:SSM框架
- Mybatis
- Spring
- SpringMVC
- 整合SSM框架,做一个小项目
要求:
课上:认真听,不懂的就问,不要摆烂
课下:要慢慢完善自己的项目
这周的任务:分组-选出组长-定题(做什么项目)-写需求文档-研究git(项目管理工具)
1 mybatis框架
课程计划:
1、MyBatis快速入门
2、MyBatis对数据库中数据的增删改查操作
3、#{}占位符的应用
4、动态SQL的应用
5、MyBatis的Mapper接口开发
1.1 MyBatis简介(了解)
1.1.1 什么是MyBatis
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL语句 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索以及释放资源等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
总之,Mybatis对JDBC访问数据库的过程进行了封装,简化了JDBC代码,解决JDBC将结果集封装为Java对象的麻烦。
下图是MyBatis架构图:
(1)mybatis-config.xml是Mybatis的核心配置文件,通过其中的配置可以生成SqlSessionFactory,也就是SqlSession工厂
(2)基于SqlSessionFactory可以生成SqlSession对象
(3)SqlSession是一个既可以发送SQL去执行,并返回结果,类似于JDBC中的Connection对象,也是Mybatis中至关重要的一个对象。
(4)Executor是SqlSession底层的对象,用于执行SQL语句
(5)MapperStatement对象也是SqlSession底层的对象,用于接收输入映射(SQL语句中的参数),以及做输出映射(即将SQL查询的结果映射成相应的结果)
1.1.2 为什么要使用MyBatis
思考:在开始之前,思考下如何通过JDBC查询Emp表中的所有记录,并封装到一个List
1、使用传统方式JDBC访问数据库:
(1)使用JDBC访问数据库有大量重复代码(比如注册驱动、获取连接、获取传输器、释放资源等);
(2)JDBC自身没有连接池,会频繁的创建连接和关闭连接,效率低;
(3)SQL是写死在程序中,一旦修改SQL,需要对类重新编译;
(4)对查询SQL执行后返回的ResultSet对象,需要手动处理,有时会特别麻烦;
...
2、使用mybatis框架访问数据库:
(1)Mybatis对JDBC对了封装,可以简化JDBC代码;
(2)Mybatis自身支持连接池(也可以配置其他的连接池),因此可以提高程序的效率;
(3)Mybatis是将SQL配置在mapper文件中,修改SQL只是修改配置文件,类不需要重新编译。
(4)对查询SQL执行后返回的ResultSet对象,Mybatis会帮我们处理,转换成Java对象。
...
总之,JDBC中所有的问题(代码繁琐、有太多重复代码、需要操作太多对象、释放资源、对结果的处理太麻烦等),在Mybatis框架中几乎都得到了解决!!
1.2 MyBatis快速入门
1.2.1 准备数据,创建库和表
| -- 1、创建数据库 yonghedb 数据库 create database if not exists yonghedb charset utf8; use yonghedb; -- 选择yonghedb数据库 -- 2、删除emp表(如果存在) drop table if exists emp; -- 3、在 yonghedb 库中创建 emp 表 create table emp( id int primary key auto_increment, name varchar(50), job varchar(50), salary double ); -- 4、往 emp 表中, 插入若干条记录 insert into emp values(null, '黄皓轩', '程序员', 3300); insert into emp values(null, '杜欣峰', '程序员', 2800); insert into emp values(null, '方艺璇', '程序员鼓励师', 2700); insert into emp values(null, 'Ravi', '部门总监', 4200); insert into emp values(null, '谢邦贤', '程序员', 3000); insert into emp values(null, '董长春', '程序员', 3500); select * from emp; |
1.2.2 创建工程,导入所需jar包、创建测试类
1、创建Maven的java工程
2、导入junit、mysql、mybaits等开发包
在pom.xml文件中引入相关依赖包即可
3、创建cn.niit.mybatis.TestMybatis01测试类,并提供findAll方法(查询emp表中所有的员工信息),开发步骤如下:
| public class TestMybatis { //通过Junit提供的@Test注解,来执行此方法 //双击方法名,右键 -- run as -- JUnit Test @Test public void findAll(){//通过mybatis查询所有的员工数据select * from emp //1、读取mybatis的核心配置文件(mybatis-config.xml)
//2、通过mybatis核心配置文件中的内容获取一个SqlSessionFactory对象
//3、通过SqlSessionFactory获取SqlSession对象
//4、执行指定的sql语句(EmpMapper.xml),获取查询结果
//5、输出结果 } } |
1.2.3 创建mybatis-config.xml文件
1、在src/main/resources目录下,创建mybatis-config.xml文件 (MyBatis的核心配置文件)
2、mybatis-config.xml文件配置如下:
|
PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
|
1.2.4 添加EmpMapper.xml文件
1、在src/main/resources目录下,创建EmpMapper.xml文件 (实体类的映射文件)
2、EmpMapper.xml文件配置如下:
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
select * from emp
|
1.2.5 添加并编写Emp实体类
注意:在当前实例中,Emp类中的属性和数据库表的字段名称必须一致,否则将会无法将结果集封装到Java对象中。
在src/main/java目录下创建cn.niit.pojo.Emp类并实现Emp类:
提供私有属性、构造方法以及对应的getter方法、setter方法,并重写toString
| public class Emp { //属性私有 private Integer id; private String name; private String job; private Double salary; //无参构造 public Emp() { super(); } //全参构造 public Emp(Integer id, String name, String job, Double salary) { super(); this.id = id; this.name = name; this.job = job; this.salary = salary; } //get+set方法 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } //重写toString @Override public String toString() { return "Emp [id=" + id + ", name=" + name + ", job=" + job + ", salary=" + salary + "]"; } } |
1.2.6 实现测试类,并测试
1、实现findAll方法,代码如下:
| public class TestMybatis { //通过Junit提供的@Test注解,来执行此方法 //双击方法名,右键 -- run as -- JUnit Test @Test public void findAll() throws Exception{//通过mybatis查询所有的员工数据select * from emp //1、读取mybatis的核心配置文件(mybatis-config.xml) InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //2、通过mybatis核心配置文件中的内容获取一个SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); //3、通过SqlSessionFactory获取SqlSession对象 //默认是false表示手动提交事务,传true则会自动提交事务 SqlSession sqlSession = sqlSessionFactory.openSession(true); //4、执行指定的sql语句(EmpMapper.xml),获取查询结果 //如果查询的结果只有一条数据则调用selectOne(),如果结果不知一条数据,需要调用selectList() List //5、输出结果 for (Emp emp : list) { System.out.println(emp); } } } |
2、执行findAll方法,输出结果为:
如果没有结果,则到Junit窗口中查看报错信息找Cause关键字
1.3 MyBatis入门细节
1.3.1 mybatis-config.xml配置文件
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
configuration是根标签,当前文件中所有的配置都在该标签内,注意其中配置的关键点:
默认的环境 ID(比如:default="develop")。
每个 environment 元素定义的环境 ID(比如:id="develop")。
事务管理器的配置(比如:type="JDBC")。
数据源的配置(比如:type="POOLED")。
(1)environments标签:该标签内部可以配置多个environment,即多种环境,每种环境可以做不同配置或连接不同数据库。例如,开发、测试、生产环境可能需要不同的配置,连接的数据库可能也不相同,因此我们可以配置三个environment,分别对应上面三种不同的环境。
但是要记住,environment可以配置多个,但是最终要使用的只能是其中一个!
(2)environment标签:内部可以配置多种配置信息,下面介绍事务管理配置和数据源配置。
(3)transactionManage标签:事务管理配置,mybatis中有两种事务管理方式,也就是 type="[JDBC|MANAGED]。
JDBC:这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务范围。推荐使用。
MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接。需要自己手动添加并管理。不推荐使用。
(4)dataSource标签:数据源,也就是连接池配置。这里type指定数据源类型,有三种内建的类型:JNDI、POOLED、UNPOOLED
JNDI:已过时,不推荐使用!
POOLED:使用连接池,mybatis会创建连接池,并从连接池中获取连接访问数据库,在操作完成后,将会把连接返回连接池。
UNPOOLED:不使用连接池,该方式适用于只有小规模数量并发用户的简单应用程序上。
(5)mappers标签:用于导入mapper文件的位置,其中可以配置多个mapper,即可以导入多个mapper文件。
1.3.2 EmpMapper.xml配置文件
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
select * from emp
(1)第1行是xml的文档声明,用于声明xml的版本和编码
(2)第2、3、4行,引入了xml约束文档,当前xml文档将会按照mybatis-3-mapper.dtd文件所要求的规则进行书写。
(3)Mapper标签:根标签,其中namespace(名称空间,也叫命名空间),要求不能重复。其实就是一个名称,一般我们指定为"包名+文件名"。
(4)select标签:用于指定将来要执行的各种SQL语句。标签上可以声明属性,下面介绍常用的属性:id、resultType、resultMap
id属性:要求值不能重复。将来在执行SQL时,可以通过namespace + id找到指定SQL并执行。
resultType属性:从这条SQL语句中返回所期望类型的类的完全限定名称(包名+类名)。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。
简而言之,resultType控制查询SQL执行后返回值的类型或集合中的泛型,例如查询emp表中的单条记录,返回值是一个Emp对象,因此,resultType="cn.niit.pojo.Emp";
如果查询emp表中的多条记录,返回值是一个List
resultMap属性:复杂对象结构(例如多表关联查询等)。 使用 resultType 或 resultMap,但不能同时使用。
总结1.4 MyBatis增删改查
1.4.1 新增员工
1、编辑EmpMapper.xml文件, 添加新增员工对应的sql.
insert into emp value(null, '刘德华', '歌手', 8888)
将获取SqlSession对象的代码写到静态代码块中
| private static SqlSession sqlSession; static{ //在静态代码块中为初始化sqlSession的值 try { //1、读取mybatis的核心配置文件(mybatis-config.xml) InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //2、通过mybatis核心配置文件中的内容获取一个SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); //3、通过SqlSessionFactory获取SqlSession对象 //默认是false表示手动提交事务,传true则会自动提交事务 sqlSession = sqlSessionFactory.openSession(true); } catch (Exception e) { e.printStackTrace(); } } |
2、编写TestMybatis类,添加testInsert方法,实现新增员工操作。
| //练习2: 新增员工信息: 刘德华 歌手 8888 @Test public void testInsert(){ //通过sqlSession执行指定的sql语句 int rows = sqlSession.update("EmpMapper.insert"); //注意如果openSession没有传true则需要下行代码手动提交事务,否则更新操作会回滚 //sqlSession.commit(); System.out.println("影响的行数:"+rows); } |
1.4.2 修改员工
1、编辑EmpMapper.xml文件, 添加新增员工对应的sql.
update emp set job='演员', salary=88888 where name='刘德华'
2、编写TestMybatis类,添加testUpdate方法,实现修改员工信息。
| //练习3: 修改员工信息, 将刘德华的job改为'演员' salary改为88888 @Test public void testUpdate(){ int rows = sqlSession.update("EmpMapper.update"); System.out.println("影响的行数:"+rows); } |
1.4.3 删除员工
1、编辑EmpMapper.xml文件, 添加新增员工对应的sql.
delete from emp where name='刘德华'
2、编写TestMybatis类,添加testDelete方法,实现删除员工。
| //练习4: 删除name为'刘德华'的记录 @Test public void testDelete(){ int rows = sqlSession.update("EmpMapper.delete"); System.out.println("影响的行数:"+rows); } |
为了更好地查看最终执行的SQL语句,我们整合一下log4j日志框架
1.4.4 #{}占位符
在上面的增删改查操作中,SQL语句中的值是写死在SQL中,而在实际开发中,此处的值往往是用户提交过来的值,因此这里我们需要将SQL中写死的值替换为占位符。
#{}占位符: 相当于JDBC中的问号(?)占位符,是为SQL语句中的值进行占位,如果参数值是字符串或者日期类型,会进行转义处理
${}占位符: 是为SQL片段进行占位,是将传过来的SQL片段直接拼接在SQL语句中,不会做任何的转义处理。
使用占位符完成上面的增删改查练习
练习5、查询:查询指定id的员工信息
mapper文件配置:
select * from emp where id=#{id}
java代码示例:
@Test
public void testFindById() {
//执行sql语句, 返回执行结果
Emp emp = session.selectOne(
"EmpMapper.findById", 1 );
System.out.println( emp );
}
练习6:往emp表中添加一个新员工
当参数不止一个时,多个参数可以选择用map封装也可以选择用POJO对象封装
用map封装数据
| // 练习6: 新增员工信息: 张飞 Java开发工程师 15000
@Test public void testInsert2(){ //1、将多个参数数据,封装到map集合中,传过去
Map map.put("name", "张飞"); map.put("job", "Java开发工程师"); map.put("sal", 15000); int rows = sqlSession.update("EmpMapper.insert2", map); System.out.println("影响的行数:"+rows); } |
mapper文件:
| insert into emp value(null,#{name},#{job},#{sal})
|
用POJO对象封装数据
| @Test public void testInsert3(){ //1、将多个参数数据,封装到pojo对象中,传过去
Emp emp = new Emp(); emp.setName("关羽"); emp.setJob("保安"); emp.setSalary(8000.0); int rows = sqlSession.update("EmpMapper.insert3", emp); System.out.println("影响的行数:"+rows); } |
mapper文件
| insert into emp value(null,#{name},#{job},#{salary}) |
练习7:修改员工信息: 张飞 架构师 25000
mapper文件配置:
update emp set job=#{job}, salary=#{salary}
where name=#{name}
java代码示例:
| //练习7: 修改员工信息: 张飞 架构师 25000 @Test public void testUpdate2(){ //将数据封装到emp对象中 Emp emp = new Emp(null, "张飞", "架构师", 25000.0); int rows = sqlSession.update("EmpMapper.update2", emp); System.out.println("影响的行数:"+rows); } |
练习8:删除指定id的员工信息
mapper文件配置:
delete from emp where id=#{id}
java代码示例:
public void testDelete2() throws IOException{
......
//执行SQL语句
int rows = session.delete("EmpMapper.delete2", 1);
System.out.println("影响行数:"+rows);
}
1.4.5 ${}占位符
在上面的增删改查练习中,当SQL语句中包含的参数值是传递过来的,在SQL语句中我们会通过 #{} 占位符进行占位,在SQL语句真正执行时,再将传递过来的值替换SQL语句中的占位符。
其实,#{} 就是JDBC中的问号(?)占位符,因此为了安全考虑,在执行时会对传递过来的值进行转译处理。
例如:查询指定name的员工信息,SQL语句为:
select * from emp where name=#{name}
其实就等价于JDBC中: select * from emp where name=?,如果传过来的参数值为:张三,那么最终执行的SQL语句为:
select * from emp where name='张三'
那么如果我们在传递的时候不是一个参数值,而是一个SQL片段呢?
例如在查询时,我们想动态的传递查询的列:
select 查询的列 from emp
此时传递过来的应该是一个SQL片段,不同于上面的参数值,如果此时还用 #{},也会像上面一样被转译处理,这不是我们希望看到的。
如果不想让传过来的SQL片段被转译处理,而是拼接在SQL语句中,那么这里可以使用 ${},例如:
select ${columns} from emp
示例1:查询emp表中所有员工的名称(name)、职位(job):
mapper文件配置:
| select ${cols} from emp;
|
java代码示例:
| // 练习9: 动态显示要查询的列select ${cols} from emp; @Test public void testSelectCols(){ //用${}即使只有一个数据,也需要用map封装 Map map = new HashMap(); //map.put("cols", "*"); map.put("cols", "id,name"); //执行sql List //打印结果 for (Emp emp : list) { System.out.println(emp); } } |
示例2: 根据name模糊查询emp表
使用${}实现模糊查询
| select * from emp where name like '%${name}%' |
| //练习10: 根据name模糊查询,用户可能传张、方 //select * from emp where name like '%${name}%' @Test public void testFindByName(){ //封装数据 Map map = new HashMap<>(); map.put("name", "张"); //执行sql语句 List //打印结果 for (Emp emp : list) { System.out.println(emp); } } |
使用#{}占位符实现模糊查询
| select * from emp where name like #{name} |
| @Test public void testFindByName2(){ //加入用户传的是张 String name = "张"; //执行sql语句 List //打印结果 for (Emp emp : list) { System.out.println(emp); } } |
需要注意的是,在传递 ${}对应的值时,需要将值存入map集合中!!
总结:在大多数情况下还是使用#{}占位符,而${}多用于为不带引号的字符串进行占位!
1.5 动态SQL标签
1.5.1 if、where元素
(1)mybatis中的if元素用于对某一字段进行判断,从而决定是否执行包含在其中的SQL片段。
(2)where元素则用于对包含在其中的SQL语句进行检索,需要时会添加where关键字,还会剔除多余的连接词(比如and或者or)
示例:查询emp表中所有员工的信息,另:
如果传递了minSal(最低薪资)和maxSal(最高薪资),则查询薪资大于minSal和小于maxSal的员工信息;
如果只传递了minSal,则查询薪资大于minSal的所有员工信息;
如果只传递了maxSal,则查询薪资小于maxSal的所有员工信息;
通过传一个等式解决SQL语法问题
| select * from emp where 1=1 and salary > #{minSal}
and salary < #{maxSal}
|
| @Test public void testSelectSal(){ //模拟用户从传的值 double minSal = 3000; double maxSal = 5000; //用map封装多个参数数据 Map map = new HashMap(); map.put("minSal", minSal); map.put("maxSal", maxSal); //执行sql语句并传参 List //打印结果 for (Emp emp : list) { System.out.println(emp); } } |
通过where标签解决SQL语法问题
|
select * from emp and salary > #{minSal}
and salary < #{maxSal}
|
1.5.2 foreach元素
mapper文件配置:
| delete from emp where id in
#{id}
|
Java代码示例:
| //练习14: 根据员工的id批量删除员工信息2,3,5,8 @Test public void testDeleteByIds(){ //模拟用户传的值2,3,5,8 Integer[] ids = {2,3,5,8}; //执行sql语句批量删除员工数据 int rows = sqlSession.update("EmpMapper.deleteByIds", ids); System.out.println("影响的行数:"+rows); } |
mapper文件配置:
| sal} where id in (4,6,9) --> update emp set salary = salary + #{sal} where id in #{id}
|
Java代码示例:
| //练习15: 根据员工的id批量更新员工信息 //将id为xxxx的员工的薪资涨xxx //将id为4、6、9的员工的薪资涨1000 update emp set salary = salary + #{sal} where id in @Test public void testUpdateSal(){ //模拟,假如用户传的涨的薪资为1000 int sal2 = 1000; //加入用户选中的要涨薪的员工id为4、6、9 Integer[] ids = {4,6,9}; //用map封装数据 Map map = new HashMap(); map.put("sal", sal2); map.put("ids", ids); //执行sql语句并传值 int rows = sqlSession.update("EmpMapper.UpdateBySal", map); System.out.println("影响的行数:"+rows); } |
练习:根据员工id批量查询指定id的员工信息
select * from emp where id in(4,6,9,10);
1.6 Mapper接口开发
1.6.1 Mapper接口开发介绍
在上面的Mybatis案例中, 通过SqlSession对象调用方法进行增删改查操作时, 方法中需要传入的第一个参数是一个字符串值, 该值对应的内容为: (Mapper文件中的) namespace + id, 通过这种方式, 找到Mapper文件中映射的SQL语句并执行!!
这种方式由于传入的是字符串值, 很容易发生字符串拼写错误且编译时期不会提示。
这里我们将会讲解比上面更加简单的方式,也是我们企业开发中最常用的方式,即使用mapper接口开发。使用mapper接口开发需要注意以下几点:
1、创建一个接口,接口的全路径名和mapper文件的namespace值要相同
2、mapper文件中每条要执行的SQL语句,在接口中要添加一个对应的方法,并且接口中的方法名和SQL标签上的id值相同
3、Mapper接口中方法接收的参数类型,和mapper.xml中定义的sql的接收的参数类型要相同
4、接口中方法的返回值类型和SQL标签上的resultType即返回值类型相同(如果方法返回值是集合,resultType只需要指定集合中的泛型)
1.6.2 Mapper接口开发实现
下面将使用mapper接口开发的方式,实现根据id查询指定的员工信息
1、创建cn.niit.dao.EmpMapper接口
由于接口的全路径名(cn.niit.dao.EmpMapper)要和EmpMapper.xml的namespace值保持一致, 因此, 这里将namespace的值改为cn.niit.dao.EmpMapper:
在核心配置文件中配置mapper文件
接口:
Mapper文件
2、通过Mapper接口开发执行Sql,查询所有员工数据
mapper文件
| select * from emp |
接口
| //此接口和EmpMapper2.xml文件对应 public interface EmpMapper { //mapper文件中的每条sql语句都对应接口中的一个方法 //且方法名要和sql语句的id一致,接收的参数类型和返回值的结果类型也要一致 public List } |
测试方法
| //===========Mapper接口开发=============
//执行EmpMapper2.xml中的findAll @Test public void testFindAll(){ //List //执行findAll方法
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);//传的参数为对应接口的字节码文件 //findAll方法底层也是由mybatis帮我们实现的,它就是通过接口的全限定类名+方法名,找到了要执行sql语句 List //打印结果 for(Emp e:list){ System.out.println(e); } } |
注意:方法的名字要和映射的sql标签的id值保持一致
方法的返回值类型和resultType的类型要一致
mapper接口测试2:新增员工信息如:赵云 保安 9000
mapper文件:
| insert into emp value(null,#{name},#{job},#{salary});
|
Mapper接口
| //此接口和EmpMapper2.xml文件对应 public interface EmpMapper { //mapper文件中的每条sql语句都对应接口中的一个方法 //且方法名要和sql语句的id一致,接收的参数类型和返回值的结果类型也都要一致 public List
public int insert(Emp emp); } |
测试方法
| //mapper接口测试2:新增员工信息如:赵云 保安 9000 @Test public void testInsert(){ EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); //封装参数数据 Emp emp = new Emp(null,"赵云", "保安", 9000.0); //执行sql语句 int rows = mapper.insert(emp); System.out.println("影响的行数:"+rows); } |
mapper接口练习:修改员工信息如:赵云 保镖 29000
update emp set job = #{job}, salary = #{salary} where name = #{name}
1.7 加入log4j日志框架
导入log4J的jar包
在项目中加入log4j的配置文件,用于打印日志信息,便于开发调试。
在src(或相似的目录)下创建log4j.properties如下:
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
mybatis默认使用log4j作为输出日志信息。
只要将该文件放在指定的位置,log4j工具会自动到指定位置加载上述文件,读取文件中的配置信息并使用!
1.8 mybatis-config文件没有提示的解决办法
如果在没有网络(外网)的情况下,编写mybatis-config.xml文件没有提示,可以按照下面的步骤进行配置:
(1)找到mybatis-3-config.dtd的文件的位置,例如:
(2)复制下面的url地址:
http://mybatis.org/dtd/mybatis-3-config.dtd
配置mapper文件则复制此url http://mybatis.org/dtd/mybatis-3-mapper.dtd
(3)在eclipse菜单栏中: window --> Preferences --> 在搜索框中搜索 [ xml ]
XML --> XML Catalog --> User Specified Entries --> Add…
2 spring框架
2.1 spring简介
2.1.1 什么是Spring?
spring是分层的JavaSE及JavaEE应用于全栈的轻量级开源框架,以IoC(Inverse Of Control:控制反转/反转控制)和AOP(Aspact Oriented Programming:面向切面编程)为核心,提供了表现层SpringMVC和持久层Spring JDBC以及业务层事务管理等众多模块的企业级应用技术,还能整合开源世界中众多著名的第三方框架和类库,逐渐成为使用最多的JavaEE企业应用开源框架。
SSH : Struts2 Spring Hibernate
SSM : Springmvc Spring Mybatis
Spring的本质是管理软件中的对象,即创建对象和维护对象之间的关系
2.1.2 Spring的发展历程
1997 年 IBM提出了EJB 的思想
1998 年,SUN制定开发标准规范 EJB1.0
1999 年,EJB1.1 发布
2001 年,EJB2.0 发布
2003 年,EJB2.1 发布
2006 年,EJB3.0 发布
Rod Johnson (罗德·约翰逊,spring 之父)
Expert One-to-One J2EE Design and Development(2002)
阐述了 J2EE 使用EJB 开发设计的优点及解决方案
Expert One-to-One J2EE Development without EJB(2004)
阐述了 J2EE 开发不使用 EJB的解决方式(Spring 雏形)
2.1.3 Spring的优势
1).方便解耦,简化开发
通过 Spring提供的 IoC容器,可以将对象间的依赖关系交由 Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为较为底层的需求编写代码,可以更专注于上层的应用。
2).AOP 编程的支持
通过 Spring的 AOP 功能,方便进行面向切面的编程,许多不容易用传统OOP(Object Oriented Programming:面向对象编程) 实现的功能可以通过 AOP 轻松应付。
3).声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
4).方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
5).方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
6).降低 JavaEE API 的使用难度。
Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
7).Spring框架源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java 设计模式灵活运用以及对 Java技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
2.1.4 spring的架构
Spring 最初的目标就是要整合一切优秀资源,然后对外提供一个统一的服务。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如下图所示:
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
|
| 模块 | 说明 |
| 1. | 核心容器Spring Core | 核心容器,提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式,将应用程序的配置和依赖性规范与实际的应用程序代码分开。 |
| 2. | Spring Context | Spring上下文,是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。 |
| 3. | Spring AOP | 通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。可以很容易地使 Spring框架管理的任何对象支持AOP。Spring AOP模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,就可以将声明性事务管理集成到应用程序中。 |
| 4. | Spring DAO | JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。 |
| 5. | Spring ORM | Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括JDO、Hibernate和iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。 |
| 6. | Spring Web | Web上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以Spring 框架支持与 Jakarta Struts的集成。Web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。 |
| 7. | Spring MVC框架 | MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。 |
Bean: 可重用组件(计算机英语)
JavaBean:使用Java语言编写的可重用组件,例如:service层、dao层等
JavaBean:通常分为业务Bean和实体Bean
业务Bean:处理业务逻辑,service层、dao层
实体Bean:封装数据,例如,为了封装员工信息而编写的Emp实体类
2.2 Spring IOC控制反转
2.3.1 什么是控制反转
IOC(Inverse Of Control)控制反转,即,把创建对象的权利交给框架。
也就是将指将对象的创建、对象的存储、对象的管理交给了spring容器。
(spring容器是spring中的一个核心模块,用于管理对象,底层可以理解为是一个map集合)
------------------------------------------------------------------
在此之前,当需要对象时,通常是利用new关键字创建一个对象:
但由于new对象,会提高类和类之间的依赖关系,即代码之间的耦合性。
而现在我们可以将对象的创建交给框架来做:
2.3 IOC入门案例
下面将使用spring的IOC解决程序间的耦合
2.3.1 创建Maven工程,引入spring相关依赖包
1、创建Maven—Java工程
2、引入junit、spring的jar包:在maven工程的pom.xml文件的根标签(project)内添加如下配置:
| |
导入后保存pom文件,项目如图所示:
2.3.2 创建spring核心配置文件—applicationContext.xml
1、在工程的src/main/resources源码目录下,创建applicationContext.xml文件:
2、在applicationContext.xml中添加文件头信息:
| 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"> |
3、准备类User+UserInfo和测试类TestSpring
User
| package cn.niit.pojo; //用户类,用来封装用户信息的 //私有化属性+构造方法(无参构造和全参构造)+get和set方法+toString方法 public class User { private Integer userId; private String userName; private Integer age; private String addr; private UserInfo userInfo; //无参构造 public User() { super(); } //全参构造 public User(Integer userId, String userName, Integer age, String addr, UserInfo userInfo) { super(); this.userId = userId; this.userName = userName; this.age = age; this.addr = addr; this.userInfo = userInfo; } //get和set方法 public Integer getUserId() { return userId; } public void setUserId(Integer userId) { System.out.println("调用了setUserId方法,userId的值为:"+userId); this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } public UserInfo getUserInfo() { return userInfo; } public void setUserInfo(UserInfo userInfo) { this.userInfo = userInfo; } //重写toString @Override public String toString() { return "User [userId=" + userId + ", userName=" + userName + ", age=" + age + ", addr=" + addr + ", userInfo=" + userInfo + "]"; } } |
UserInfo
| public class UserInfo { } |
4、将User类的对象交给Spring管理
| 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"> |
5、测试步骤及代码如下:
| public class TestSpring { @Test//从容器中获取User类的对象,而不是通过new的方式获取 public void testIOC(){ //读取spring核心配置文件,拿到ClassPathXmlApplicationContext对象 //传的参数就是核心配置文件所在的路径(默认从类目录开始找) ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); //从容器获取配置了的User对象 User user = (User)ac.getBean("user"); System.out.println(user); } } |
6、入门案例总结:
这就是spring框架的IOC——控制反转。之前我们自己new对象,例如:
User u = new User();
而现在,变成由一个初始化的xml配置文件来创建,也就是由spring容器来创建。
User user = (User) ac.getBean("user");
当程序运行,spring开始工作后,会加载整个xml核心配置文件,读取到
2.4 Bean对象的单例和多例
2.4.1 Bean对象的单例和多例概述
在Spring容器中管理的Bean对象的作用域,可以通过scope属性或用相关注解指定其作用域。
最常用是singleton(单例)或prototype(多例)。其含义如下:
1) singleton:单实例,是默认值。这个作用域标识的对象具备全局唯一性。
当把一个 bean 定义设置scope为singleton作用域时,那么Spring IOC容器只会创建该bean定义的唯一实例。也就是说,整个Spring IOC容器中只会创建当前类的唯一一个对象。
这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都 将返回被缓存的、唯一的这个对象实例。
singleton负责对象的创建、初始化、销毁。
2) prototype:多实例。这个作用域标识的对象每次获取都会创建新的对象。
当把一个 bean 定义设置scope为prototype作用域时,Spring IOC容器会在每一次获取当前Bean时,都会产生一个新的Bean实例
prototype只负责对象的创建和初始化,不负责销毁。
2.4.2 为什么用单实例或多实例?
之所以用单实例,在没有线程安全问题的前提下,没必要每个请求都创建一个对象,这样子既浪费CPU又浪费内存;
之所以用多实例,是为了防止并发问题;即一个请求改变了对象的状态(例如,可改变的成员变量),此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;
用单例和多例的标准只有一个:当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),使用多实例,否则单实例;
在Spring中配置Bean实例是单例还是多例方法是:
单例:
多例:
2.4.3 测试spring的单实例和多实例
测试代码如下:
| @Test//测试user类的对象是单实例还是多实例 public void testSingleton(){ User u1 = (User)ac.getBean("user"); User u2 = (User)ac.getBean("user"); if(u1 == u2){//==判断的是内存地址,地址相同则一定是同一个对象 System.out.println("当前实例是单实例"); }else{ System.out.println("当前实例是多实例"); } } |
2.5 Spring DI依赖注入
2.5.1 两种注入方式介绍
DI(Dependency Injection)依赖注入
依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。
简单来说,所谓的依赖注入其实就是,在创建对象的同时或之后,如何给对象的属性赋值。
如果对象由我们自己创建,这一切都变得很简单,例如:
User user = new User();
user.setName("张三");
user.setAge(18);
或者:
User user = new User("张三", 18);
如果对象由spring创建,那么spring是怎么给属性赋值的?spring提供两种方式为属性赋值:
(1).Set方式注入
(2).构造方法注入
2.6 set方式注入
2.6.1 普通属性注入
修改applicationContext.xml中User实例的声明,为User实例注入属性值
| |
普通属性直接通过value注入即可
执行测试方法:
| //测试spring的依赖注入--为对象的属性赋值 @Test public void testDI(){ User user = (User)ac.getBean("user"); System.out.println(user); } |
控制台的输出结果为:
2.6.2 对象属性注入
需求:为User对象的userInfo属性(对象属性)赋值
| |
由于此处是将UserInfo对象作为值赋值给另一个对象的属性,因此ref属性的值,为UserInfo对象bean标签的id值。
对象属性通过ref属性注入。
运行测试方法,结果为:
2.7 构造方法注入
需求:通过Spring创建User对象,并为User对象的属性(name、age、UserInfo属性)赋值
1、为User类声明构造函数
//声明无参构造函数
public User() {
}
//声明有参构造函数
public User(String name, Integer age, UserInfo userInfo) {
super();
this.name = name;
this.age = age;
this.userInfo = userInfo;
}
2、修改applicationContext.xml文件,将set方式修改为构造方法注入。
| super(); this.userId = userId; this.userName = userName; this.age = age; this.addr = addr; this.userInfo = userInfo; } 通过constructor-arg标签实现 name属性的值,需要和形参的名字一致 --> |
其中,constructor-arg标签name属性的值必须和构造函数中参数的名字相同!
同样的,普通属性直接通过value注入即可;对象属性通过ref属性注入。
运行测试类方法,结果为:
3 springmvc框架
3.1 MVC设计模式
3.1.1 什么是设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。
设计模式使代码编写真正工程化;
设计模式是软件工程的基石脉络,如同大厦的结构一样。
设计模式就是一种模子,经过多年实践锤炼形成一套行之有效的完成某个特定任务的步骤和方式。
3.1.2 MVC设计模式
MVC设计模式是一种通用的软件编程思想
在MVC设计模式中认为, 任何软件都可以分为三部分组成:
(1)控制程序流转的控制器(Controller)
(2)封装数据处理数据的模型(Model)
(3)负责展示数据的视图(view)
并且在MVC设计思想中要求一个符合MVC设计思想的软件应该保证上面这三部分相互独立,互不干扰,每一个部分只负责自己擅长的部分。
如果某一个模块发生变化,应该尽量做到不影响其他两个模块。这样做的好处是,软件的结构会变得更加的清晰,可读性强。有利于后期的扩展和维护,并且代码可以实现复用。
3.2 初识SpringMVC
3.2.1 Servlet的缺点
1、通常情况下,一个Servlet类只负责处理一个请求,若项目中有成百上千个请求需要处理,就需要有成百上千个Servlet类,这样会使得项目中Servlet类的个数暴增;
2、在Servlet3.0版本之前,每一个Servlet都需要在web.xml文件中至少做八行配置信息,配置内容多且繁琐。当Servlet特别多时,web.xml配置量太多,不利于团队开发;
3、当通过客户端提交参数到服务器,通过Servlet进行接收时,无论数据本身是什么格式,在Servlet中一律按照字符串进行接收,后期需要进行类型转换,复杂类型还需要特殊处理,特别麻烦!
String paramValue = request.getParameter(String paramName);
springmvc参数绑定,
4、servlet具有容器依赖性,必须放在服务器中运行,不利于单元测试;
...
3.2.2 SpringMVC简介
Springmvc是spring框架的一个模块,spring和springmvc无需中间整合层整合
Springmvc是一个基于mvc的web框架
3.2.3 springmvc执行原理
(1).用户发送请求 至 前端控制器(DispatcherServlet);
提示:DispatcherServlet的作用:接收请求,调用其它组件处理请求,响应结果,相当于转发器、中央处理器,是整个流程控制的中心
(2).前端控制器(DispatcherServlet)收到请求后调用处理器映射器(HandlerMapping)
处理器映射器(HandlerMapping)找到具体的Controller(可以根据xml配置、注解进行查找),并将Controller返回给DispatcherServlet;
(3).前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)。处理器适配器经过适配调用具体的Controller;(Controller--> service --> Dao --> 数据库)
Controller执行完成后返回ModelAndView,
提示:Model(模型数据,即Controller处理的结果,Map) View(逻辑视图名,即负责展示结果的JSP页面的名字)
处理器适配器(HandlerAdapter)将controller执行的结果(ModelAndView)返回给前端控制器(DispatcherServlet);
(4).前端控制器(DispatcherServlet)将执行的结果(ModelAndView)传给视图解析器(ViewReslover)
视图解析器(ViewReslover)根据View(逻辑视图名)解析后返回具体JSP页面
(5).前端控制器(DispatcherServlet)根据Model对View进行渲染(即将模型数据填充至视图中);
前端控制器(DispatcherServlet)将填充了数据的网页响应给用户。
其中整个过程中需要开发人员编写的部分有 Controller、Service、Dao、View;
3.3 springmvc入门案例
需求:
(1)通过浏览器访问 http://主机名称:端口/项目名称/hello 地址,在控制台输出 "hello springmvc"
(2)将请求转向 /WEB-INF/pages/home.jsp 页面
3.3.1 创建Maven—Javaweb工程
项目报错,缺少web.xml和WEB-INF目录,需要生成
3.3.2 在pom.xml中引入springmvc所需jar包
|
|
3.3.3 创建springmvc的核心配置文件springmvc-config
3.3.4 在web.xml中配置前端控制器
|
|
3.3.5 配置springmvc-config.xml
直接复制下面配置文件的内容即可!
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> class="org.springframework.web.servlet.view.InternalResourceViewResolver"> |
3.3.6 创建并实现HelloController类
实现HelloController类
| @Controller public class HelloController {
@RequestMapping("/hello")//表示此方法处理的请求url为/hello public String hello(){ System.out.println("hello springmvc");
return "home"; } } |
3.3.7 创建并实现home.jsp
在WEB-INF/pages/目录下,创建home.jsp页面
| <%@ page pageEncoding="UTF-8"%>
这是home.jsp |
3.3.8 访问测试
打开浏览器,输入url地址:http://localhost/hello 地址,访问结果如下:
3.4 springmvc参数绑定
当项目中引入springmvc框架后,所有的请求流转将由springmvc进行控制,当客户端发送的请求中包含数据(也就是请求参数)时,那么该如何在controller层获取这些参数呢?
springmvc会自动的将请求中包含的参数和方法的参数进行匹配,也就是说只要保证,请求中的参数名称和方法中的参数名称相对应(另,参数的格式也要正确),在方法中就可以使用这些参数—即请求中的参数。
3.4.1 简单类型参数绑定
当需要获取客户端发送过来的少量数据时,可以在Controller中声明的方法上,通过声明方法参数对这些参数一个一个进行接收,具体示例如下:
| @RequestMapping("param1") public String testParam(String name,Integer age){ //打印请求参数 System.out.println("name"+name); System.out.println("age"+age); return "home"; } |
3、访问Controller中的param1方法,注意将name和age参数一起发送给服务器:
控制台输出结果为:
3.4.2 包装类型参数绑定
当请求参数数据比较多时,一个个接收太麻烦,可以用一个pojo对象来接收
1、准备一个用于接收用户数据的pojo类User
| public class User { private String name; private Integer age; //无参构造 public User() { super(); } //全参构造 public User(String name, Integer age) { super(); this.name = name; this.age = age; } //get+set方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } //toString()方法 @Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; } } |
2、添加param2方法,用于接收对象类型的参数,代码实现如下:
| @RequestMapping("param2") public String testParam2(User user){ //打印请求参数 System.out.println("name:"+user.getName()); System.out.println("age:"+user.getAge()); return "home"; } |
3、访问Controller中的param2方法,注意将name和age参数一起发送给服务器:
控制台输出结果为:
3.4.3 日期类型参数绑定
Springmvc默认支持的日期格式为yyyy/MM/dd hh:mm:ss
1、在Controller类中添加param3方法,代码实现如下:
| @RequestMapping("param3") public String testParam3(Date date){ //打印请求参数 System.out.println("date:"+date); return "home"; }
@InitBinder public void InitBinder (ServletRequestDataBinder binder){ binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"), true) ); } |
由于springmvc默认的日期格式是yyyy/MM/dd,因此如果日期参数是 yyyy-MM-dd 格式,就会出现400错误!!
2、解决方案:
在springmvc中,提供了@InitBinder注解,用于指定自定义的日期转换格式,因此,我们只需要在Controller类中添加上面的代码即可,在接受日期类型的参数时,会自动按照自定义的日期格式进行转换。
3.5 跳转和乱码处理
3.5.1 实现转发(forward)
关于资源跳转两种方式:请求转发和重定向
请求转发是服务器内部资源的一种跳转方式,即当浏览器发送请求访问服务器中的某一个资源时,该资源将请求转交给另一个资源进行处理的过程,就叫请求转发。
请求转发和重定向都能实现资源跳转,但是跳转的过程有所不同。
请求转发的特点
|
什么是重定向:当浏览器向服务器发送请求访问某一个资源A时,资源A在响应时通知浏览器需要再进一步向资源B发请求才能获取到对应的资源,浏览器再次向服务器发送请求访问资源B,最终是由资源B响应浏览器要获取的数据,这个过程叫重定向。
重定向的特点:
|
在servlet中是通过request对象实现请求转发,同样的,springmvc也提供了请求转发的方式,具体实现如下:
| @RequestMapping("/testForward") public String testForward(){ System.out.println("testForward方法执行了~"); //将请求转发给testForward2来处理然后响应 return "forward:testForward2"; } @RequestMapping("/testForward2") public String testForward2(){ System.out.println("testForward2方法执行了~"); //将请求转发给home.jsp页面响应 return "home"; } |
结果:
控制台:
3.5.2 实现重定向(redirect)
在servlet中,是通过response对象实现请求重定向
同样的,springmvc也提供了请求重定向的方式,具体实现如下:
| @RequestMapping("/testRedirect") public String testRedirect(){ System.out.println("testRedirect方法执行了~"); //将请求重定向给testRedirect2 return "redirect:testRedirect2"; } @RequestMapping("/testRedirect2") public String testRedirect2(){ System.out.println("testRedirect2方法执行了~"); //将请求转发给home.jsp页面响应 return "home"; } |
结果:
控制台:
3.5.3 乱码处理
在前面的Servlet学习中,我们学习了GET和POST请求参数乱码如何解决。
准备form.html页面来测试GET提交和POST提交
| "/>
GET提交
POST提交
|
springmvc也提供了解决请求参数乱码的方案,就是在web.xml中加入如下代码,可以解决POST提交参数乱码:
|
|
注意:改了配置文件后需要重启tomcat服务器
Servlet中,两种请求方式乱码解决方案回顾:
(1)如果请求方式为POST提交,必然会出现乱码,解决方式是在任何获取参数的代码之前,添加如下代码:
request.setCharacterEncoding("utf-8");
(2)如果请求方式为GET提交,tomcat8及之后的版本已经解决了中文参数乱码的问题,不需要处理,tomcat7及之前的版本只需要在 [tomcat]/conf/server.xml中添加如下配置也可以解决乱码问题。
关闭控制台自动弹出



