Mybatis(笔记)
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sVjVvSsH-1609421261302)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201230155751713.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-T7eTtpZo-1609421261311)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201230155810111.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OR4rnozx-1609421261314)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201230155900100.png)]
基礎(chǔ)知識
JDBC Mysql Java基礎(chǔ) Maven Junit框架:是有配置文件的。最好的方式:看官網(wǎng)文檔
簡介
什么是 MyBatis?
MyBatis 是一款優(yōu)秀的持久層框架 它支持定制化 SQL、存儲過程以及高級映射。 MyBatis 避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。 MyBatis 可以使用簡單的 XML 或注解來配置和映射原生類型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 對象)為數(shù)據(jù)庫中的記錄。如何獲得MyBatis?
Maven倉庫<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.3</version> </dependency>github:https://github.com/mybatis/mybatis-3/releases中文文檔:https://mybatis.org/mybatis-3/zh/index.html1.2、持久化
數(shù)據(jù)持久化
持久化就是將程序的數(shù)據(jù)在持久狀態(tài)和瞬時狀態(tài)轉(zhuǎn)化的過程 內(nèi)存:斷電即失 數(shù)據(jù)庫(JDBC),io文件持久化。 生活:冷藏、罐頭。為什么需要持久化?
有一些對象,不能讓他丟掉。1.3、持久層
Dao層、Service層、Controller層…
完成持久化工作的代碼塊 層界限十分明顯為什么需要Mybatis?
幫助程序員將數(shù)據(jù)存入到數(shù)據(jù)庫中。方便傳統(tǒng)的JDBC代碼太復(fù)雜了。簡化、框架、自動化。不用Mybatis也可以。更容易上手。技術(shù)沒有高低之分優(yōu)點:簡單易學(xué)靈活sql和代碼的分離,提高了可維護性。提供映射標(biāo)簽,支持對象與數(shù)據(jù)庫的orm字段關(guān)系映射提供對象關(guān)系映射標(biāo)簽,支持對象關(guān)系組建維護提供xml標(biāo)簽,支持編寫動態(tài)sql。最重要的一點:使用的人多!
Spring SpringMVC SpringBoot
第一個Mybatis程序
思路:搭建環(huán)境–>導(dǎo)入Mybatis–>編寫代碼–>測試!
搭建環(huán)境
搭建數(shù)據(jù)庫
CREATE DATABASE mybatis;
use mybatis;
CREATE TABLE user(
id INT(20) not null PRIMARY KEY,
name VARCHAR(30) DEFAULT NULL,
pwd VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO user (id,name,pwd) VALUES
(1,‘狂神’,‘123456’),
(2,‘張三’,‘123456’),
(3,‘李四’,‘123890’)
新建項目
新建一個普通的maven項目 刪除src目錄 導(dǎo)入maven依賴 <?xml version="1.0" encoding="UTF-8"?>4.0.0
<!--父工程--> <groupId>com.rui</groupId> <artifactId>MyBatis-Study</artifactId> <version>1.0-SNAPSHOT</version><!--導(dǎo)入依賴--> <dependencies><!--mysql驅(qū)動--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.17</version></dependency><!--mybatis--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.2</version></dependency><!--junit--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency> </dependencies><build><resources><resource><directory>src/main/resources</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes></resource><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>true</filtering></resource></resources> </build>創(chuàng)建一個模塊
& xml
編寫mybatis的核心需要轉(zhuǎn)義配置文件<?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><!--environments配置環(huán)境組--><!--default默認(rèn)環(huán)境--><environments default="development"><!--environment單個環(huán)境--><environment id="development"><!--transactionManager配置事務(wù)管理器--><transactionManager type="JDBC"/><!--配置連接池--><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UFT-8"/><property name="username" value="root"/><property name="password" value="Cc105481"/></dataSource></environment></environments></configuration>編寫mybatis工具類
package com.rui.utils;import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException; import java.io.InputStream;//sqlSessionFactory—>SessionFactory public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try{ //使用mybatis第一步、獲取sqlSessionFactory對象 String resource = “mybatis-config.xml”; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); }catch(IOException e) { e.printStackTrace(); } } //既然有了 SqlSessionFactory,顧名思義,我們就可以從中獲得 SqlSession 的實例了。 // SqlSession 完全包含了面向數(shù)據(jù)庫執(zhí)行 SQL 命令所需的所有方法。 // 你可以通過 SqlSession 實例來直接執(zhí)行已映射的 SQL 語句。 public static SqlSession getSqlSession(){return sqlSessionFactory.openSession(); } }編寫代碼
實體類package com.rui.pojo;public class user {private int id;private String name;private String pwd;public user() {}public user(int id, String name, String pwd) {this.id = id;this.name = name;this.pwd = pwd;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPwd() {return pwd;}public void setPwd(String pwd) {this.pwd = pwd;}@Overridepublic String toString() {return "user{" +"id=" + id +", name='" + name + '\'' +", pwd='" + pwd + '\'' +'}';} }Dao接口package com.rui.dao;import com.rui.pojo.User;import java.util.List;public interface UserDao {List<User> getUserList(); }接口實現(xiàn)類由原來的UserImpl轉(zhuǎn)變?yōu)橐粋€Mapper配置文件 <?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.kuang.dao.UserDAo"> <select id="getUserList" resultType="com.kuang.pojo.User"> select * from mybatis.user </select> </mapper>2.4、測試
注意點:
org.apache.ibatis.binding.BindingException: Type interface com.rui.dao.UserDao is not known to the MapperRegistry.
MapperRegistry是什么?
核心配置文件中注冊mappers
junit測試package com.rui;import com.rui.dao.UserDao; import com.rui.pojo.User; import com.rui.utils.MyBatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test;import java.util.List;public class UserDaoTest {@Testpublic void test(){//第一步:獲得SqlSession對象SqlSession sqlSession = MyBatisUtils.getSqlSession();//執(zhí)行SQLUserDao mapper = sqlSession.getMapper(UserDao.class);List<User> userList = mapper.getUserList();for (User user : userList) {System.out.println(user);}//關(guān)閉SqlSessionsqlSession.close();} }可能會遇到的問題:配置文件沒有注冊綁定接口錯誤方法名不對返回類型不對Maven導(dǎo)出資源問題CRUD
namespace
namespace中的包名要和Dao/mapper接口的包名保持一致
select
選擇查詢語句;
id:就是對應(yīng)的namespace中的方法名; resultType:Sql語句執(zhí)行的返回值! parameterType:參數(shù)類型!編寫接口package com.rui.dao;import com.rui.pojo.User;import java.util.List;public interface UserMapper {//根據(jù)id查詢用戶User getUserById(int id); }編寫對應(yīng)的mapper中的sql語句<select id="getUserById" resultType="com.rui.pojo.User" parameterType="int">/*定義sql*/select * from mybatis.user where id = #{id};</select>測試@Testpublic void getUserById(){SqlSession sqlSession = MyBatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.getUserById(1);System.out.println(user);sqlSession.close();}Insert
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sgtpnsff-1609421261322)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201230191728648.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HtENm566-1609421261324)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201230191959575.png)]
Update
Delete
注意點:增刪改需要提交事務(wù)
分析錯誤
標(biāo)簽不要匹配錯誤 resource綁定mapper,需要使用路徑! 程序配置文件必須符合規(guī)范 NullPointerException,沒有注冊到資源 輸出的xml文件中存在中文亂碼問題 maven資源沒有導(dǎo)出問題[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4sqxOFdI-1609421261325)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201230192720390.png)]
萬能Map
假設(shè),我們的實體類,或者數(shù)據(jù)庫中的表,字段或者參數(shù)過多,我們應(yīng)當(dāng)考慮使用Map!
//萬能Map
int addUser2(Map<String,Object> map);
//萬能map
@Test
public void addUser2(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put(“userId”,4);
map.put(“userName”,“王五”);
map.put(“password”,“23333”);
mapper.addUser2(map);
//提交事務(wù)
sqlSession.commit();
sqlSession.close();
}
Map傳遞參數(shù),直接在sql中取出key即可!【parameterType=“map”】
對象傳遞參數(shù),直接在sql中取對象的屬性即可!【parameterType=“Object”】
只有一個基本類型參數(shù)的情況下,可以直接在sql中取到!
多個參數(shù)用Map,或者注解!
思考
模糊查詢怎么寫?
Java代碼執(zhí)行的時候傳遞通配符%%List<User> userList=mapper.getUserLike("%李%");在sql拼接中使用通配符!select * from mybatis.user where name like "%"#{value}"%"mybatis解決 1 字節(jié)的 UTF-8 序列的字節(jié) 1 無效
xmls上的utf-8變?yōu)閡tf8
配置解析xml
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-SXCE1SQr-1609421261327)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201230203824739.png)]
核心配置文件
mybatis-config.xml MyBatis的配置文件包含了會深深影響MyBatis行為的設(shè)置和屬性信息configuration(配置)
properties(屬性)
settings(設(shè)置)
typeAliases(類型別名)
typeHandlers(類型處理器)
objectFactory(對象工廠)
plugins(插件)
environments(環(huán)境配置)
environment(環(huán)境變量)
transactionManager(事務(wù)管理器)
dataSource(數(shù)據(jù)源)
databaseIdProvider(數(shù)據(jù)庫廠商標(biāo)識)
mappers(映射器)
環(huán)境配置(environments)
MyBatis 可以配置成適應(yīng)多種環(huán)境
不過要記住:盡管可以配置多個環(huán)境,但每個 SqlSessionFactory 實例只能選擇一種環(huán)境。
學(xué)會使用配置多套運行環(huán)境!
MyBatis默認(rèn)的事務(wù)管理器就是JDBC,連接池:POOLED
屬性(properties)
我們可以通過properties屬性來實現(xiàn)引用配置文件
這些屬性都是可外部配置且可動態(tài)替換的,既可以在典型的 Java 屬性文件中配置,亦可通過 properties 元素的子元素來傳遞。【db.properties】
編寫一個配置文件
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?
useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=Cc105481
在核心配置文件中引入
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XOIjXGPl-1609421261328)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201230211034703.png)]
<properties resource="db.properties"><property name="username" value="root"/><property name="password" value="Cc105481"/> </properties>可以直接引入外部文件 可以在其中增加一些屬性配置 如果兩個文件有同一字段,優(yōu)先使用外部配置文件的!類型別名(typeAliases)
類型別名是為 Java 類型設(shè)置一個短的名字。存在的意義僅在于用來減少類完全限定名的冗余。1. <!--可以給實體類起別名--><typeAliases><typeAlias type="com.rui.pojo.User" alias="User"/></typeAliases>也可以指定一個包名,MyBatis 會在包名下面搜索需要的 Java Bean,比如:2.掃描實體類的包,他的默認(rèn)別名就為這個類的類名,首字母小寫!<!--可以給實體類起別名--><typeAliases><package name="com.rui.pojo"/></typeAliases>在實體類比較少的時候,使用第一種方式。
如果實體類十分多,建議使用第二種方式。
第一種可以DIY別名,第二種則不行,如果非要改,需要在實體類(pojo)上增加@Alias注解
@Alias(“author”)
public class Author {
…
}
映射器(mappers)
MapperRegistry:注冊綁定我們的Mapper文件;
方式一: 【推薦使用】
<!--每一個Mapper.XML都需要在Mybatis核心配置文件中注冊!--> <mappers><mapper resource="com/kuang/dao/UserMapper.xml"/> </mappers>方式二:使用class文件綁定注冊
<!--每一個Mapper.XML都需要在Mybatis核心配置文件中注冊!--> <mappers><mapper class="com.kuang.dao.UserMapper"/> </mappers>注意點:
- 接口和他的Mapper配置文件必須同名!
- 接口和他的Mapper配置文件必須在同一個包下!
方式三:使用掃描包進行注入綁定
<!--每一個Mapper.XML都需要在Mybatis核心配置文件中注冊!--> <mappers><package name="com.kuang.dao"/> </mappers>注意點:
- 接口和他的Mapper配置文件必須同名!
- 接口和他的Mapper配置文件必須在同一個包下!
練習(xí)時間:
- 將數(shù)據(jù)庫配置文件外部引入
- 實體類別名
- 保證UserMapper 接口 和 UserMapper .xml 改為一致!并且放在同一個包下!
生命周期和作用域
生命周期,和作用域是至關(guān)重要的,因為錯誤的使用會導(dǎo)致非常嚴(yán)重的并發(fā)問題。
SqlSessionFactoryBuilder
mybatis運行流程SqlSessionFactory:
可以想象為:數(shù)據(jù)庫連接池 SqlSessionFactory 一旦被創(chuàng)建就應(yīng)該在應(yīng)用的運行期間一直存在,沒有任何理由丟棄它或重新創(chuàng)建另一個實例。 因此SqlSessionFactory的最佳作用域是應(yīng)用作用域。SqlSession
連接到連接池的一個請求!SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。用完之后需要趕緊關(guān)閉,否則會占用資源SqlSessionFactory這里的每一個Mapper,就代表一個具體的業(yè)務(wù)!
xml vs db.properties
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> url=jdbc:mysql://localhost:3306/mybatis? useSSL=true&useUnicode=true&characterEncoding=utf8解決屬性名和字段名不一致的問題
1、問題
數(shù)據(jù)庫中的字段
數(shù)據(jù)庫中的字段
新建一個項目,拷貝之前的,測試實體類字段不一致的情況。
public class User {
private int id;
private String name;
private String pwd;
}
測試錯誤
//select * from mybatis.user where id = #{id}
//類型處理器
//select id,name,pwd from mybatis.user where id = #{id}
解決方法:
起別名select id,name,pwd as password from mybatis.user where id = #{id}
2、resultMap
結(jié)果集映射
id name pwd
id name password
日志
日志工廠
如果一個數(shù)據(jù)庫操作,出現(xiàn)了異常,我們需要排錯。日志就是最好的助手!
曾經(jīng):sout、debug
現(xiàn)在:日志工廠
日志
SLF4J
LOG4J【掌握】
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING【掌握】
NO_LOGGING
在Mybatis中具體使用那個日志實現(xiàn),在設(shè)置中設(shè)定!
STDOUT_LOGGING標(biāo)準(zhǔn)日志輸出(控制臺)
在mybatis核心配置文件中,配置我們的日志!
日志配置
<settings><setting name="logImpl" value="STDOUT_LOGGING"/> </settings>Log4j
什么叫l(wèi)og4j
Log4j是Apache的一個開源項目,通過使用Log4j,我們可以控制日志信息輸送的目的地是控制臺、文件、GUI組件 我們也可以控制每一條日志的輸出格式 通過定義每一條日志信息的級別,我們能夠更加細致地控制日志的生成過程。 通過一個配置文件來靈活地進行配置,而不需要修改應(yīng)用的代碼。1.先導(dǎo)入log4j的包
<dependencies><!-- https://mvnrepository.com/artifact/log4j/log4j --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency> </dependencies>2.log4j.properties
#將等級為DEBUG的日志信息輸出到console和file這兩個目的地,console和file的定義在下面的代碼 log4j.rootLogger=DEBUG,console,file#控制臺輸出的相關(guān)設(shè)置 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%c]-%m%n#文件輸出的相關(guān)設(shè)置 log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File=./log/kuang.log log4j.appender.file.MaxFileSize=10mb log4j.appender.file.Threshold=DEBUG log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n#日志輸出級別 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sql=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG3.配置log4j為日志實現(xiàn)
<settings><setting name="logImpl" value="LOG4J"/> </settings>4.log4j的使用!直接測試運行剛才的查詢
DEBUG [main] (LogFactory.java:105) - Logging initialized using ‘class org.apache.ibatis.logging.log4j.Log4jImpl’ adapter.
DEBUG [main] (LogFactory.java:105) - Logging initialized using ‘class org.apache.ibatis.logging.log4j.Log4jImpl’ adapter.
DEBUG [main] (PooledDataSource.java:353) - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] (PooledDataSource.java:353) - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] (PooledDataSource.java:353) - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] (PooledDataSource.java:353) - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] (JdbcTransaction.java:136) - Opening JDBC Connection
Loading class com.mysql.jdbc.Driver'. This is deprecated. The new driver class iscom.mysql.cj.jdbc.Driver’. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
DEBUG [main] (PooledDataSource.java:424) - Created connection 2049051802.
DEBUG [main] (JdbcTransaction.java:100) - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7a220c9a]
DEBUG [main] (BaseJdbcLogger.java:143) - ==> Preparing: /定義sql/ select * from mybatis.user where id = ?;
DEBUG [main] (BaseJdbcLogger.java:143) - > Parameters: 1(Integer)
DEBUG [main] (BaseJdbcLogger.java:143) - < Total: 1
User{id=1, name=‘狂神’, password=‘123456’}
DEBUG [main] (JdbcTransaction.java:122) - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7a220c9a]
DEBUG [main] (JdbcTransaction.java:90) - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7a220c9a]
DEBUG [main] (PooledDataSource.java:381) - Returned connection 2049051802 to pool.
Disconnected from the target VM, address: ‘127.0.0.1:58296’, transport: ‘socket’
Process finished with exit code 0
簡單使用
在要使用Log4j 的類中,導(dǎo)入org.apache.log4j.Logger;日志對象,加載參數(shù)為當(dāng)前類的classstatic Logger logger = Logger.getLogger(UserDaoTest.class);日志級別logger.info("info:進入了testLog4j方法"); logger.debug("debug:進入了testLog4j"); logger.error("error:進入了testLog4j");分頁
思考:為什么要分頁?
減少數(shù)據(jù)的處理量使用Limit分頁
select * from user limit startIndex,pageSize
使用Mybatis實現(xiàn)分頁,核心SQL
接口//分頁 List<User> getUserByLimit(Map<String,Integer> map);Mapper.xml<!--分頁--> <select id="getUserByLimit" parameterType="map" resultMap="UserMap">select * from mybatis.user limit #{startIndex},#{pageSize} </select>測試@Testpublic void getUserByLimit(){SqlSession sqlSession = MyBatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);HashMap<String, Integer> map = new HashMap<>();map.put("startIndex",0);map.put("pageSize",2);List<User> userList = mapper.getUserByLimit(map);for (User user : userList) {System.out.println(user);}sqlSession.close(); }RowBounds分頁
不再使用SQL實現(xiàn)分頁
接口
List<User> getUserByRowBounds();mapper.xml<!--分頁2--><select id="getUserByRowBounds" resultMap="UserMap">select * from mybatis.user</select>測試@Testpublic void getUserByRowBounds(){SqlSession sqlSession = MyBatisUtils.getSqlSession();//RowBounds實現(xiàn)RowBounds rowBounds = new RowBounds(1, 2);//通過java代碼層面實現(xiàn)分頁List<User> userList = sqlSession.selectList("com.rui.dao.UserMapper.getUserByRowBounds",null,rowBounds);for (User user : userList) {System.out.println(user);}sqlSession.close();}分頁插件
了解即可,萬一以后公司的架構(gòu)師,說要使用,只需要知道它是什么東西!
安裝插件
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QS8r53Ae-1609421261332)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231111258418.png)]
使用注解開發(fā)
面向接口編程的根本原因:解耦,可拓展,提高復(fù)用,分層開發(fā)中、上層不用管具體的實現(xiàn),大家都遵守共同的標(biāo)準(zhǔn),使得開發(fā)變得容易,規(guī)范性好
8.2、使用注解開發(fā)
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3mw7qTNR-1609421261335)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231112107709.png)]
注解在接口上實現(xiàn)@Select(value = "select * from user") List<User> getUsers();需要在核心配置文件中綁定接口!<!--綁定接口--> <mappers><mapper class="rui.dao.UserMapper"/> </mappers>測試public class UserMapperTest {
@Test
public void test(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//底層主要應(yīng)用反射
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
}
本質(zhì):反射機制實現(xiàn)
底層:動態(tài)代理!
CRUD
我們可以在工具類創(chuàng)建的時候?qū)崿F(xiàn)自動提交事務(wù)!
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
編寫接口,增加注解
public interface UserMapper { @Select(value = “select * from user”) List getUsers();//方法存在多個參數(shù),所有的參數(shù)前面必須加上@Param注解 @Select("select * from user where id = #{id} or name = #{name}") User getUserByID(@Param("id")int id,@Param("name")String name);@Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})") int addUser(User user);@Update("update user set name = #{name},pwd = #{password} where id = #{id}") int updateUser(User user);@Delete("delete from user where id = #{uid}") int deleteUser(@Param("uid") int id);}測試類
【注意:我們必須要將接口注冊綁定到我們的核心配置文件中!】
關(guān)于@Param()注解
基本類型的參數(shù)或者String類型,需要加上 引用類型不需要加 如果只有一個基本類型的話,可以忽略,但是建議大家都加上 我們在SQL中引用的就是我們這里的@Param()中設(shè)定的屬性名#{} ${}區(qū)別
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-j1MisDUH-1609421261336)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231004542662.png)]
Lombok
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
使用步驟:
在IDEA中安裝Lombok插件在項目中導(dǎo)入lombok的jar包<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version></dependency>在實體類上加注解即可@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
Lombok config system
說明:
@Data:無參構(gòu)造,get、set、toSring、hashcode、equals
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
多對一處理
多對一:
多個學(xué)生,對應(yīng)一個老師 對于學(xué)生這邊而言,關(guān)聯(lián)...多個學(xué)生,關(guān)聯(lián)一個老師【多對一】 對于老師而言,集合,一個老師又很多學(xué)生【一對多】SQL:
CREATE TABLE `teacher`( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, PRIMARY KEY (`id`) )ENGINE=INNODB DEFAULT CHARSET=utf8INSERT INTO teacher(`id`,`name`) VALUES (1,'秦老師');CREATE TABLE `student`( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, `tid` INT(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `fktid`(`tid`), CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`) )ENGINE=INNODB DEFAULT CHARSET=utf8INSERT INTO student(`id`,`name`,`tid`) VALUES (1,'小明',1); INSERT INTO student(`id`,`name`,`tid`) VALUES (2,'小紅',1); INSERT INTO student(`id`,`name`,`tid`) VALUES (3,'小張',1); INSERT INTO student(`id`,`name`,`tid`) VALUES (4,'小李',1); INSERT INTO student(`id`,`name`,`tid`) VALUES (5,'小王',1);[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cUcsgD1L-1609421261338)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231121234379.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zeeucWYK-1609421261344)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231122125396.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZndIVVbr-1609421261345)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231122747070.png)]
測試環(huán)境
導(dǎo)入lombok 新建實體類Teacher,Student 新建Mapper接口 建立Mapper.XML文件 在核心配置文件中綁定注冊我們的MApper接口或者文件!【方式很多,隨意選】 測試查詢是否成功!按照查詢嵌套處理
select * from student select * from teacher where id = #{id}
按照結(jié)果嵌套處理
select s.id sid,s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid=t.id;
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YnejrJb5-1609421261346)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231124701611.png)]
按照查詢嵌套處理
<!--思路:1. 查詢所有的學(xué)生信息2. 根據(jù)查詢出來的學(xué)生的tid,尋找對應(yīng)的老師! 子查詢--><select id="getStudent" resultMap="StudentTeacher">select * from student </select><resultMap id="StudentTeacher" type="Student"><result property="id" column="id"/><result property="name" column="name"/><!--復(fù)雜的屬性,我們需要單獨處理 對象: association 集合: collection --><association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> </resultMap><select id="getTeacher" resultType="Teacher">select * from teacher where id = #{id} </select>按照結(jié)果嵌套處理
<!--按照結(jié)果嵌套處理--> <select id="getStudent2" resultMap="StudentTeacher2">select s.id sid,s.name sname,t.name tnamefrom student s,teacher twhere s.tid = t.id; </select><resultMap id="StudentTeacher2" type="Student"><result property="id" column="sid"/><result property="name" column="sname"/><association property="teacher" javaType="Teacher"><result property="name" column="tname"/></association> </resultMap>回顧Mysql 多對一查詢方式:
- 子查詢
- 聯(lián)表查詢
項目名字不一致
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RyYNHSbm-1609421261351)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231130923162.png)]
后面會多一個括號
一對多處理
比如:一個老師擁有多個學(xué)生!
對于老師而言,就是一對多的關(guān)系!
環(huán)境搭建
實體類
@Data public class Student {private int id;private String name;private int tid;} @Data public class Teacher {private int id;private String name;//一個老師擁有多個學(xué)生private List<Student> students; }按照結(jié)果嵌套處理
Teacher getTeacher(@Param("tid")int id); <!--按結(jié)果嵌套查詢--><select id="getTeacher" resultMap="TeacherStudent">select s.id sid, s.name sname, t.name tname,t.id tidfrom student s,teacher twhere s.tid = t.id and t.id = #{tid}</select><resultMap id="TeacherStudent" type="Teacher"><result property="id" column="tid"/><result property="name" column="tname"/><!--復(fù)雜的屬性,我們需要單獨處理 對象: association 集合: collectionjavaType="" 指定屬性的類型!集合中的泛型信息,我們使用ofType獲取--><collection property="students" ofType="Student"><result property="id" column="sid"/><result property="name" column="sname"/><result property="tid" column="tid"/></collection></resultMap>按照查詢嵌套處理
<select id="getTeacher2" resultMap="TeacherStudent2">select * from mybatis.teacher where id = #{tid} </select><resultMap id="TeacherStudent2" type="Teacher"><collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/> </resultMap><select id="getStudentByTeacherId" resultType="Student">select * from mybatis.student where tid = #{tid} </select>小結(jié)
注意點:
- 保證SQL的可讀性,盡量保證通俗易懂
- 注意一對多和多對一中,屬性名和字段的問題!
- 如果問題不好排查錯誤,可以使用日志 , 建議使用 Log4j
慢SQL 1s 1000s
面試高頻
- Mysql引擎
- InnoDB底層原理
- 索引
- 索引優(yōu)化!
動態(tài)SQL
什么是動態(tài)SQL:動態(tài)SQL就是指根據(jù)不同的條件生成不同的SQL語句
利用動態(tài)SQL這一特性可以徹底擺脫這種痛苦
動態(tài) SQL 元素和 JSTL 或基于類似 XML 的文本處理器相似。在 MyBatis 之前的版本中,有很多元素需要花時間了解。MyBatis 3 大大精簡了元素種類,現(xiàn)在只需學(xué)習(xí)原來一半的元素便可。MyBatis 采用功能強大的基于 OGNL 的表達式來淘汰其它大部分元素。
if choose (when, otherwise) trim (where, set) foreach搭建環(huán)境
CREATE TABLE `bolg`( `id` VARCHAR(50) NOT NULL COMMENT '博客id', `title` VARCHAR(100) NOT NULL COMMENT '博客標(biāo)題', `author` VARCHAR(30) NOT NULL COMMENT '博客作者', `creat_time` DATETIME NOT NULL COMMENT '創(chuàng)建時間', `views` INT(30) NOT NULL COMMENT '瀏覽量' )ENGINE=INNODB DEFAULT CHARSET=utf8創(chuàng)建一個基礎(chǔ)工程
導(dǎo)包編寫配置文件編寫實體類@Data public class Blog {private int id;private String title;private String author;private Date creatTime;//用utilprivate int views; }編寫實體類對應(yīng)的Mapper接口和Mapper.xml[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-llp41rd9-1609421261356)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231164121408.png)]
<setting name="mapUnderscoreToCamelCase" value="true"/>IF
<select id="queryBlogIF" parameterType="map" resultType="blog">select * from mybatis.blog where 1=1<if test="title != null">and title = #{title}</if><if test="author != null">and author = #{author}</if> </select>choose (when, otherwise)
能自動去掉and
<select id="queryBlogChoose" parameterType="map" resultType="blog">select * from mybatis.blog<where><choose><when test="title != null">title = #{title}</when><when test="author != null">and author = #{author}</when><otherwise>and views = #{views}</otherwise></choose></where></select>trim (where,set)
select * from mybatis.blog <where><if test="title != null">title = #{title}</if><if test="author != null">and author = #{author}</if> </where> <update id="updateBlog" parameterType="map">update mybatis.blog<set><if test="title != null">title = #{title},</if><if test="author != null">author = #{author}</if></set>where id = #{id} </update>所謂的動態(tài)SQL,本質(zhì)還是SQL語句 , 只是我們可以在SQL層面,去執(zhí)行一個邏輯代碼
if
where , set , choose ,when
SQL片段
有的時候,我們可能會將一些功能的部分抽取出來,方便復(fù)用!
使用SQL標(biāo)簽抽取公共的部分
<sql id="if-title-author"><if test="title != null">title = #{title}</if><if test="author != null">and author = #{author}</if> </sql>在需要使用的地方使用Include標(biāo)簽引用即可
<select id="queryBlogIF" parameterType="map" resultType="blog">select * from mybatis.blog<where><include refid="if-title-author"></include></where> </select>注意事項:
- 最好基于單表來定義SQL片段!
- 不要存在where標(biāo)簽
Foreach
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XfGDnG3j-1609421261358)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231192208358.png)]
select * from user where 1=1 and <foreach item="id" collection="ids"open="(" separator="or" close=")">#{id}</foreach>(id=1 or id=2 or id=3) <!--select * from mybatis.blog where 1=1 and (id=1 or id = 2 or id=3)我們現(xiàn)在傳遞一個萬能的map , 這map中可以存在一個集合! --> <select id="queryBlogForeach" parameterType="map" resultType="blog">select * from mybatis.blog<where><foreach collection="ids" item="id" open="and (" close=")" separator="or">id = #{id}</foreach></where></select>動態(tài)SQL就是在拼接SQL語句,我們只要保證SQL的正確性,按照SQL的格式,去排列組合就可以了
建議:
- 現(xiàn)在Mysql中寫出完整的SQL,再對應(yīng)的去修改成為我們的動態(tài)SQL實現(xiàn)通用即可!
緩存簡介
查詢 : 連接數(shù)據(jù)庫,耗資源!
一次查詢的結(jié)果,給他暫存在一個可以直接取到的地方!—>內(nèi)存 : 緩存
我們再次查詢相同數(shù)據(jù)的時候,直接走緩存,就不用走數(shù)據(jù)庫了
什么事緩存[Cache]?存在內(nèi)存中的臨時數(shù)據(jù)。將用戶經(jīng)常查詢的數(shù)據(jù)放在緩存(內(nèi)存)中,用戶去查詢數(shù)據(jù)就不用從磁盤上(關(guān)系型數(shù)據(jù)庫數(shù)據(jù)文件)查詢,從緩存中查詢,從而提高查詢效率,解決了高并發(fā)系統(tǒng)的性能問題。為什么使用緩存?減少和數(shù)據(jù)庫的交互次數(shù),減少系統(tǒng)開銷,提高系統(tǒng)效率。什么樣的數(shù)據(jù)能使用緩存?經(jīng)常查詢并且不經(jīng)常改變的數(shù)據(jù)。Mybatis緩存
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-62Pk4JE2-1609421261368)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231193827608.png)]
MyBatis包含一個非常強大的查詢緩存特性,它可以非常方便地定制和配置緩存。緩存可以極大的提升查詢效率。 MyBatis系統(tǒng)中默認(rèn)定義了兩級緩存:一級緩存和二級緩存默認(rèn)情況下,只有一級緩存開啟。(SqlSession級別的緩存,也稱為本地緩存)二級緩存需要手動開啟和配置,他是基于namespace級別的緩存。為了提擴展性,MyBatis定義了緩存接口Cache。我們可以通過實現(xiàn)Cache接口來自定義二級緩存 一級緩存也叫本地緩存:SqlSession與數(shù)據(jù)庫同義詞會話期間查詢到的數(shù)據(jù)會放在本地緩存中。以后如果需要獲取相同的數(shù)據(jù),直接從緩存中拿,沒有必要再去查詢數(shù)據(jù);測試步驟:
開啟日志! 測試在一個Session中查詢兩次相同的記錄 查看日志輸出緩存失效的情況:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-h2Qk7npu-1609421261370)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231195447615.png)]
查詢不同的東西 增刪改操作,可能會改變原來的數(shù)據(jù),所以必定會刷新緩存! 查詢不同的Mapper.xml 手動清理緩存! sqlsession.clearCache(); //手動清理緩存小節(jié):一級緩存默認(rèn)是開啟的,只在一次SqlSession中有效,也就是拿到連接到關(guān)閉連接這個區(qū)間段!
一級緩存就是一個Map。
二級緩存
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-O3scunpT-1609421261372)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231195901433.png)]
二級緩存也叫全局緩存,一級緩存作用域太低了,所以誕生了二級緩存 基于namespace級別的緩存,一個名稱空間,對應(yīng)一個二級緩存; 工作機制一個會話查詢一條數(shù)據(jù),這個數(shù)據(jù)就會被放在當(dāng)前會話的一級緩存中;如果當(dāng)前會話關(guān)閉了,這個會話對應(yīng)的一級緩存就沒了;但是我們想要的是,會話關(guān)閉了,一級緩存中的數(shù)據(jù)會被保存到二級緩存中;新的會話查詢信息,就可以從二級緩存中獲取內(nèi)容;不同的mapper查出的數(shù)據(jù)會放在自己對應(yīng)的緩存(map)中;步驟:
開啟全局緩存<!--顯式的開啟全局緩存--> <setting name="cacheEnabled" value="true"/>在要使用二級緩存的Mapper中開啟<!--在當(dāng)前Mapper.xml中使用二級緩存--> <cache/> 也可以自定義參數(shù) <cache eviction="FIFO"flushInterval="60000"size="512"readOnly="true"/>測試問題:我們需要將實體類序列化!否則就會報錯java.io.NotSerializableException: com.rui.pojo.User序列化
測試
問題:我們需要將實體類序列化!否則就會報錯java.io.NotSerializableException: com.rui.pojo.User[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2V1GnZBg-1609421261374)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231200920270.png)]
小結(jié):
只要開啟了二級緩存,在同一個Mapper下就有效 所有的數(shù)據(jù)都會先放在一級緩存中; 只有當(dāng)會話提交,或者關(guān)閉的時候,才會提交到二級緩存中!自定義緩存-encache
EhCache 是一個純Java的進程內(nèi)緩存框架,具有快速、精干等特點,是Hibernate中默認(rèn)的CacheProvider。
要在程序中使用ehcache,先要導(dǎo)包!
https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache
org.mybatis.caches
mybatis-ehcache
1.1.0
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XyuT37Qe-1609421261376)(C:\Users\王東梁\AppData\Roaming\Typora\typora-user-images\image-20201231203614123.png)]
然后在mapper中指定使用ehcache緩存實現(xiàn)
<在當(dāng)前Mapper.xml中使用二級緩存>
導(dǎo)入配置文件 ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> diskStore:為緩存路徑,ehcache分為內(nèi)存和磁盤兩級,此屬性定義磁盤的緩存位置。參數(shù)解釋如下:user.home – 用戶主目錄user.dir – 用戶當(dāng)前工作目錄java.io.tmpdir – 默認(rèn)臨時文件路徑<diskStore path="java.io.tmpdir/Tmp_EhCache"/>defaultCache:默認(rèn)緩存策略,當(dāng)ehcache找不到定義的緩存時,則使用這個緩存策略。只能定義一個。name:緩存名稱。maxElementsInMemory:緩存最大數(shù)目maxElementsOnDisk:硬盤最大緩存?zhèn)€數(shù)。eternal:對象是否永久有效,一但設(shè)置了,timeout將不起作用。overflowToDisk:是否保存到磁盤,當(dāng)系統(tǒng)當(dāng)機時timeToIdleSeconds:設(shè)置對象在失效前的允許閑置時間(單位:秒)。僅當(dāng)eternal=false對象不是永久有效時使用,可選屬性,默認(rèn)值是0,也就是可閑置時間無窮大。timeToLiveSeconds:設(shè)置對象在失效前允許存活時間(單位:秒)。最大時間介于創(chuàng)建時間和失效時間之間。僅當(dāng)eternal=false對象不是永久有效時使用,默認(rèn)是0.,也就是對象存活時間無窮大。diskPersistent:是否緩存虛擬機重啟期數(shù)據(jù) Whether the disk store persists between restarts of the Virtual Machine. The default value is false.diskSpoolBufferSizeMB:這個參數(shù)設(shè)置DiskStore(磁盤緩存)的緩存區(qū)大小。默認(rèn)是30MB。每個Cache都應(yīng)該有自己的一個緩沖區(qū)。diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認(rèn)是120秒。memoryStoreEvictionPolicy:當(dāng)達到maxElementsInMemory限制時,Ehcache將會根據(jù)指定的策略去清理內(nèi)存。默認(rèn)策略是LRU(最近最少使用)。你可以設(shè)置為FIFO(先進先出)或是LFU(較少使用)。clearOnFlush:內(nèi)存數(shù)量最大時是否清除。memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,默認(rèn)策略)、FIFO(先進先出)、LFU(最少訪問次數(shù))。FIFO,first in first out,這個是大家最熟的,先進先出。LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,緩存的元素有一個hit屬性,hit值最小的將會被清出緩存。LRU,Least Recently Used,最近最少使用的,緩存的元素有一個時間戳,當(dāng)緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那么現(xiàn)有緩存元素中時間戳離當(dāng)前時間最遠的元素將被清出緩存。 <defaultCacheeternal="false"maxElementsInMemory="10000"overflowToDisk="false"diskPersistent="false"timeToIdleSeconds="1800"timeToLiveSeconds="259200"memoryStoreEvictionPolicy="LRU"/ <cachename="cloud_user"eternal="false"maxElementsInMemory="5000"overflowToDisk="false"diskPersistent="false"timeToIdleSeconds="1800"timeToLiveSeconds="1800"memoryStoreEvictionPolicy="LRU"/>
總結(jié)
以上是生活随笔為你收集整理的Mybatis(笔记)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 5500元电脑最强组装配置单?
- 下一篇: lombok进行有参无参构造出现的问题