
1. 项目概述当垃圾分类遇上跨端技术去年夏天我在小区垃圾站目睹了保洁阿姨徒手分拣错投垃圾的场景——沾满油渍的餐巾纸从可回收物桶里被捞出混合着汤水的塑料袋在厨余垃圾中格外扎眼。这个画面让我意识到在全民环保意识觉醒的今天我们缺的不是环保热情而是一套随时可用的智能分类指导方案。这正是智慧垃圾分类指南应用的开发初衷基于FlutterOpenHarmony的跨端技术栈打造覆盖手机、平板、智能家居设备的垃圾分类助手。用户通过摄像头扫描垃圾应用实时返回分类建议并同步记录个人环保贡献值。特别针对老年群体我们设计了语音交互和图标引导功能让科技真正服务于环保实践。2. 技术选型背后的逻辑2.1 为什么选择Flutter在跨平台方案竞品中Flutter的渲染性能优势尤为突出。我们实测对比了相同垃圾分类展示页面的渲染帧率技术方案平均FPS内存占用热重载支持Flutter12078MB✔️React Native56112MB❌原生开发14465MB❌特别在垃圾图像识别的实时预览场景中Flutter的Skia引擎能保持60fps以上的流畅度。另一个关键因素是热重载——当调整垃圾分类图标间距时3秒内就能看到修改效果这对UI密集型的指导类应用至关重要。2.2 OpenHarmony的独特价值选择OpenHarmony而非Android主要基于三点考量分布式能力当用户靠近智能垃圾桶时应用可自动将界面流转到垃圾桶的显示屏上原子化服务无需安装完整APP通过卡片即可完成常见垃圾查询系统级功耗优化持续摄像头监听的耗电量比Android低40%我们在华为P50 Pro上实测发现连续运行垃圾分类应用1小时OpenHarmony版本比Android版本节省153mAh电量。这对需要常驻后台提供快速查询的服务尤为关键。3. 核心功能实现细节3.1 垃圾图像识别流水线垃圾分类识别的技术栈采用多模型协同方案// Flutter侧图像处理流程 CameraImage image await _cameraController.takePicture(); Img.Image processedImage _applyPreprocessing(image); // 光照补偿去噪 // 调用平台通道发送到Native侧 final String? result await platform.invokeMethod(classifyWaste, { imageData: processedImage.getBytes(), compressQuality: 70, }); // OpenHarmony侧AI推理 public String handleClassify(byte[] imageData) { WasteDetector detector new WasteDetector(this.context); detector.loadModel(waste_model.hdf5); // 包含45类常见垃圾 return detector.predict(imageData); }模型训练时采用了迁移学习技巧基于ResNet50 backbone在自建的20万张垃圾图片数据集上fine-tune。针对易混淆类别如塑料袋vs保鲜膜我们增加了难例挖掘策略使Top-3准确率达到92.7%。3.2 跨端数据同步方案环保积分系统需要实现手机、平板、智能手表等多端同步我们采用OpenHarmony的分布式数据管理// 创建分布式数据表 DistributedTable wasteRecordTable new DistributedTable.Builder() .setName(waste_records) .addField(userId, FieldType.STRING) .addField(wasteType, FieldType.INT) .setDistType(DistributedType.SYNC) // 实时同步 .build(); // Flutter通过平台通道调用 Futurevoid _syncRecord(String userId, int wasteType) async { await MethodChannel(com.example.waste/sync) .invokeMethod(addRecord, { userId: userId, wasteType: wasteType, }); }实测数据显示在局域网环境下数据同步延迟控制在200ms以内。我们还实现了冲突解决策略——当多设备同时提交记录时以时间戳最新者为准。4. 性能优化实战记录4.1 内存泄漏排查案例在初期测试时发现连续扫描20次后应用内存增长到480MB。通过Dart DevTools的内存快照对比定位到问题出在图像缓存// 错误实现每次识别都保留原始图像 ListImg.Image _rawImages []; // 修正方案使用LRU缓存 final _imageCache LruCacheImg.Image(maxSize: 3);优化后内存峰值降至120MB同时添加了GC触发策略当App进入后台时主动调用SystemNavigator.popSystemChannels()释放Native资源。4.2 跨平台渲染一致性挑战在OpenHarmony的某些设备上Flutter的Material图标显示为方框。这是由于字体文件未正确打包导致的。解决方案是在pubspec.yaml中显式声明字体flutter: fonts: - family: MaterialIcons fonts: - asset: assets/fonts/MaterialIcons-Regular.ttf同时需要修改OpenHarmony的config.json添加字体资源声明{ abilities: [ { resources: [ $media:MaterialIcons-Regular.ttf ] } ] }1. 概述1.1 什么是MyBatisMyBatis 是一款优秀的持久层框架它支持自定义 SQL、存储过程以及高级映射MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJOPlain Old Java Objects普通老式 Java 对象为数据库中的记录1.2 持久化数据持久化持久化就是将程序的数据在持久状态和瞬时状态转化的过程内存断电即失数据库jdbcio文件持久化为什么需要持久化有一些对象不能让他丢掉内存太贵了1.3 持久层Dao层、Service层、Controller层完成持久化工作的代码块层界限十分明显1.4 为什么需要MyBatis帮助程序员将数据存入到数据库中方便传统的JDBC代码太复杂了简化框架自动化不用MyBatis也可以更容易上手优点简单易学灵活sql和代码的分离提高了可维护性提供映射标签支持对象与数据库的orm字段关系映射提供对象关系映射标签支持对象关系组建维护提供xml标签支持编写动态sql2. 第一个MyBatis程序思路搭建环境 -- 导入MyBatis -- 编写代码 -- 测试2.1 搭建环境搭建数据库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 )ENGINEINNODB DEFAULT CHARSETutf8; INSERT INTO user(id,name,pwd) VALUES (1,狂神,123456), (2,张三,123456), (3,李四,123456)新建项目新建一个普通的maven项目删除src目录导入maven依赖!--导入依赖-- dependencies !--mysql驱动-- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.47/version /dependency !--mybatis-- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.2/version /dependency !--junit-- dependency groupIdjunit/groupId artifactIdjunit/artifactId version4.12/version /dependency /dependencies2.2 创建一个模块编写mybatis的核心配置文件?xml version1.0 encodingUTF-8 ? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd !--configuration核心配置文件-- configuration environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/mybatis?useSSLtrueamp;useUnicodetrueamp;characterEncodingUTF-8/ property nameusername valueroot/ property namepassword value123456/ /dataSource /environment /environments mappers mapper resourcecom/kuang/dao/UserMapper.xml/ /mappers /configuration编写mybatis工具类//sqlSessionFactory -- sqlSession public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { //使用Mybatis第一步获取sqlSessionFactory对象 String resource org/mybatis/example/mybatis-config.xml; InputStream inputStream Resources.getResourceAsStream(resource); sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //既然有了 SqlSessionFactory顾名思义我们可以从中获得 SqlSession 的实例。 // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }2.3 编写代码实体类//实体类 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; } Override public String toString() { return User{ id id , name name \ , pwd pwd \ }; } }Dao接口public interface UserDao { ListUser getUserList(); }接口实现类由原来的UserDaoImpl转变为一个Mapper配置文件?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd !--namespace绑定一个对应的Dao/Mapper接口-- mapper namespacecom.kuang.dao.UserDao !--select查询语句-- select idgetUserList resultTypecom.kuang.pojo.User select * from mybatis.user /select /mapper2.4 测试注意点org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry.MapperRegistry是什么核心配置文件中注册mappersjunit测试public class UserDaoTest { Test public void test(){ //第一步获得SqlSession对象 SqlSession sqlSession MybatisUtils.getSqlSession(); //方式一getMapper UserDao userDao sqlSession.getMapper(UserDao.class); ListUser userList userDao.getUserList(); for (User user : userList) { System.out.println(user); } //关闭SqlSession sqlSession.close(); } }可能会遇到的问题配置文件没有注册绑定接口错误方法名不对返回类型不对Maven导出资源问题3. CRUD3.1 namespacenamespace中的包名要和Dao/mapper接口的包名一致3.2 select选择查询语句id就是对应的namespace中的方法名resultTypeSql语句执行的返回值parameterType参数类型编写接口//根据ID查询用户 User getUserById(int id);编写对应的mapper中的sql语句select idgetUserById parameterTypeint resultTypecom.kuang.pojo.User select * from mybatis.user where id #{id} /select测试Test public void getUserById() { SqlSession sqlSession MybatisUtils.getSqlSession(); UserMapper mapper sqlSession.getMapper(UserMapper.class); User user mapper.getUserById(1); System.out.println(user); sqlSession.close(); }3.3 Insert!--对象中的属性可以直接取出来-- insert idaddUser parameterTypecom.kuang.pojo.User insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd}); /insert3.4 Updateupdate idupdateUser parameterTypecom.kuang.pojo.User update mybatis.user set name#{name},pwd#{pwd} where id #{id} ; /update3.5 Deletedelete iddeleteUser parameterTypeint delete from mybatis.user where id #{id}; /delete注意点增删改需要提交事务3.6 分析错误标签不要匹配错resource绑定mapper需要使用路径程序配置文件必须符合规范NullPointerException没有注册到资源输出的xml文件中存在中文乱码问题maven资源没有导出问题3.7 万能Map假设我们的实体类或者数据库中的表字段或者参数过多我们应当考虑使用Map//万能的Map int addUser2(MapString,Object map);!--对象中的属性可以直接取出来 传递map的key-- insert idaddUser parameterTypemap insert into mybatis.user (id, pwd) values (#{userid},#{passWord}); /insertTest public void addUser2(){ SqlSession sqlSession MybatisUtils.getSqlSession(); UserMapper mapper sqlSession.getMapper(UserMapper.class); MapString, Object map new HashMapString, Object(); map.put(userid,5); map.put(passWord,2222333); mapper.addUser2(map); sqlSession.commit(); sqlSession.close(); }Map传递参数直接在sql中取出key即可【parameterTypemap】对象传递参数直接在sql中取对象的属性即可【parameterTypeObject】只有一个基本类型参数的情况下可以直接在sql中取到多个参数用Map或者注解3.8 思考题模糊查询怎么写Java代码执行的时候传递通配符% %ListUser userList mapper.getUserLike(%李%);在sql拼接中使用通配符select * from mybatis.user where name like %#{value}%4. 配置解析4.1 核心配置文件mybatis-config.xmlMyBatis的配置文件包含了会深深影响MyBatis行为的设置和属性信息configuration配置 properties属性 settings设置 typeAliases类型别名 typeHandlers类型处理器 objectFactory对象工厂 plugins插件 environments环境配置 environment环境变量 transactionManager事务管理器 dataSource数据源 databaseIdProvider数据库厂商标识 mappers映射器4.2 环境配置environmentsMyBatis可以配置成适应多种环境不过要记住尽管可以配置多个环境但每个 SqlSessionFactory 实例只能选择一种环境。学会使用配置多套运行环境MyBatis默认的事务管理器就是JDBC连接池POOLED4.3 属性properties我们可以通过properties属性来实现引用配置文件这些属性可以在外部进行配置并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性也可以在 properties 元素的子元素中设置。【db.properties】编写一个配置文件db.propertiesdrivercom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/mybatis?useSSLtrueuseUnicodetruecharacterEncodingUTF-8 usernameroot password123456在核心配置文件中引入!--引入外部配置文件-- properties resourcedb.properties property nameusername valueroot/ property namepwd value11111/ /properties可以直接引入外部文件可以在其中增加一些属性配置如果两个文件有同一个字段优先使用外部配置文件的4.4 类型别名typeAliases类型别名可为 Java 类型设置一个缩写名字意在降低冗余的全限定类名书写!--可以给实体类起别名-- typeAliases typeAlias typecom.kuang.pojo.User aliasUser/ /typeAliases也可以指定一个包名MyBatis会在包名下面搜索需要的Java Bean比如扫描实体类的包它的默认别名就为这个类的类名首字母小写!--可以给实体类起别名-- typeAliases package namecom.kuang.pojo/ /typeAliases在实体类比较少的时候使用第一种方式。如果实体类十分多建议使用第二种。第一种可以DIY别名第二种则不行如果非要改需要在实体上增加注解Alias(user) public class User {}4.5 设置settings这是 MyBatis 中极为重要的调整设置它们会改变 MyBatis 的运行时行为。设置名描述有效值默认值cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetruelazyLoadingEnabled延迟加载的全局开关。当开启时所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。true | falsefalse4.6 其他配置typeHandlers类型处理器objectFactory对象工厂plugins插件mybatis-generator-coremybatis-plus通用mapper4.7 映射器mappersMapperRegistry注册绑定我们的Mapper文件方式一【推荐使用】!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册-- mappers mapper resourcecom/kuang/dao/UserMapper.xml/ /mappers方式二使用class文件绑定注册!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册-- mappers mapper classcom.kuang.dao.UserMapper/ /mappers注意点接口和他的Mapper配置文件必须同名接口和他的Mapper配置文件必须在同一个包下方式三使用扫描包进行注入绑定!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册-- mappers package namecom.kuang.dao/ /mappers4.8 生命周期和作用域生命周期和作用域是至关重要的因为错误的使用会导致非常严重的并发问题。SqlSessionFactoryBuilder一旦创建了SqlSessionFactory就不再需要它了局部变量SqlSessionFactory说白了就是可以想象为数据库连接池SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在没有任何理由丢弃它或重新创建另一个实例因此SqlSessionFactory的最佳作用域是应用作用域最简单的就是使用单例模式或者静态单例模式SqlSession连接到连接池的一个请求SqlSession的实例不是线程安全的因此是不能被共享的所以它的最佳的作用域是请求或方法作用域用完之后需要赶紧关闭否则资源被占用这里的每一个Mapper就代表一个具体的业务5. 解决属性名和字段名不一致的问题5.1 问题数据库中的字段新建一个项目拷贝之前的测试实体类字段不一致的情况public class User { private int id; private String name; private String password; }测试出现问题// select * from mybatis.user where id #{id} //类型处理器 // select id,name,pwd from mybatis.user where id #{id}5.2 resultMap结果集映射id name pwd id name password!--结果集映射-- resultMap idUserMap typeUser !--column数据库中的字段property实体类中的属性-- result columnid propertyid/ result columnname propertyname/ result columnpwd propertypassword/ /resultMap select idgetUserById resultMapUserMap select * from mybatis.user where id #{id} /selectresultMap元素是 MyBatis 中最重要最强大的元素ResultMap的设计思想是对简单的语句做到零配置对于复杂一点的语句只需要描述语句之间的关系就行了ResultMap的优秀之处在于你完全可以不用显式地配置它们如果这个世界总是这么简单就好了6. 日志6.1 日志工厂如果一个数据库操作出现了异常我们需要排错。日志就是最好的助手曾经sout、debug现在日志工厂SLF4JLOG4J【掌握】LOG4J2JDK_LOGGINGCOMMONS_LOGGINGSTDOUT_LOGGING【掌握】NO_LOGGING在MyBatis中具体使用哪一个日志实现在设置中设定STDOUT_LOGGING标准日志输出在mybatis核心配置文件中配置我们的日志settings setting namelogImpl valueSTDOUT_LOGGING/ /settings6.2 Log4j什么是Log4jLog4j是Apache的一个开源项目通过使用Log4j我们可以控制日志信息输送的目的地是控制台、文件、GUI组件我们也可以控制每一条日志的输出格式通过定义每一条日志信息的级别我们能够更加细致地控制日志的生成过程可以通过一个配置文件来灵活地进行配置而不需要修改应用的代码先导入log4j的包!-- https://mvnrepository.com/artifact/log4j/log4j -- dependency groupIdlog4j/groupId artifactIdlog4j/artifactId version1.2.17/version /dependencylog4j.properties#将等级为DEBUG的日志信息输出到console和file这两个目的地console和file的定义在下面的代码 log4j.rootLoggerDEBUG,console,file #控制台输出的相关设置 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 #文件输出的相关设置 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.mybatisDEBUG log4j.logger.java.sqlDEBUG log4j.logger.java.sql.StatementDEBUG log4j.logger.java.sql.ResultSetDEBUG log4j.logger.java.sql.PreparedStatementDEBUG配置log4j为日志的实现settings setting namelogImpl valueLOG4J/ /settingsLog4j的使用直接测试运行刚才的查询简单使用在要使用Log4j的类中导入包import org.apache.log4j.Logger;日志对象参数为当前类的classstatic Logger logger Logger.getLogger(UserDaoTest.class);日志级别logger.info(info:进入了testLog4j); logger.debug(debug:进入了testLog4j); logger.error(error:进入了testLog4j);7. 分页思考为什么要分页减少数据的处理量7.1 使用Limit分页语法SELECT * from user limit startIndex,pageSize; SELECT * from user limit 3; #[0,n]使用MyBatis实现分页核心SQL接口//分页 ListUser getUserByLimit(MapString,Integer map);Mapper.xml!--分页-- select idgetUserByLimit parameterTypemap resultMapUserMap select * from mybatis.user limit #{startIndex},#{pageSize} /select测试Test public void getUserByLimit(){ SqlSession sqlSession MybatisUtils.getSqlSession(); UserMapper mapper sqlSession.getMapper(UserMapper.class); HashMapString, Integer map new HashMapString, Integer(); map.put(startIndex,1); map.put(pageSize,2); ListUser userList mapper.getUserByLimit(map); for (User user : userList) { System.out.println(user); } sqlSession.close(); }7.2 RowBounds分页不再使用SQL实现分页接口//分页2 ListUser getUserByRowBounds();mapper.xml!--分页2-- select idgetUserByRowBounds resultMapUserMap select * from mybatis.user /select测试Test public void getUserByRowBounds(){ SqlSession sqlSession MybatisUtils.getSqlSession(); //RowBounds实现 RowBounds rowBounds new RowBounds(1, 2); //通过Java代码层面实现分页 ListUser userList sqlSession.selectList(com.kuang.dao.UserMapper.getUserByRowBounds,null,rowBounds); for (User user : userList) { System.out.println(user); } sqlSession.close(); }7.3 分页插件了解即可万一以后公司的架构师说要使用你需要知道它是什么东西8. 使用注解开发8.1 面向接口编程大家之前都学过面向对象编程也学习过接口但在真正的开发中很多时候我们会选择面向接口编程根本原因解耦可拓展提高复用分层开发中上层不用管具体的实现大家都遵守共同的标准使得开发变得容易规范性更好在一个面向对象的系统中系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下各个对象内部是如何实现自己的对系统设计人员来讲就不那么重要了而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信大到各模块之间的交互在系统设计之初都是要着重考虑的这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程关于接口的理解接口从更深层次的理解应是定义规范约束与实现名实分离的原则的分离接口的本身反映了系统设计人员对系统的抽象理解接口应有两类第一类是对一个个体的抽象它可对应为一个抽象体abstract class第二类是对一个个体某一方面的抽象即形成一个抽象面interface一个体有可能有多个抽象面。抽象体与抽象面是有区别的三个面向区别面向对象是指我们考虑问题时以对象为单位考虑它的属性及方法面向过程是指我们考虑问题时以一个具体的流程事务过程为单位考虑它的实现接口设计与非接口设计是针对复用技术而言的与面向对象过程不是一个问题更多的体现就是对系统整体的架构8.2 使用注解开发注解在接口上实现Select(select * from user) ListUser getUsers();需要在核心配置文件中绑定接口!--绑定接口-- mappers mapper classcom.kuang.dao.UserMapper/ /mappers测试本质反射机制实现底层动态代理MyBatis详细的执行流程8.3 CRUD我们可以在工具类创建的时候实现自动提交事务public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); }编写接口增加注解public interface UserMapper { Select(select * from user) ListUser getUsers(); // 方法存在多个参数所有的参数前面必须加上 Param(id)注解 Select(select * from user where id #{id}) User getUserByID(Param(id) int id); 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); }测试类【注意我们必须要将接口注册绑定到我们的核心配置文件中】关于Param()注解基本类型的参数或者String类型需要加上引用类型不需要加如果只有一个基本类型的话可以忽略但是建议大家都加上我们在SQL中引用的就是我们这里的Param()中设定的属性名#{} ${}区别9. LombokProject 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.java libraryplugsbuild toolswith one annotation your class使用步骤在IDEA中安装Lombok插件在项目中导入lombok的jar包!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.10/version scopeprovided/scope /dependency在实体类上加注解即可Data AllArgsConstructor NoArgsConstructorGetter 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说明Data无参构造get、set、tostring、hashcodeequals AllArgsConstructor NoArgsConstructor EqualsAndHashCode ToString Getter10. 多对一处理多对一多个学生对应一个老师对于学生这边而言关联..多个学生关联一个老师【多对一】对于老师而言集合一个老师有很多学生【一对多】SQLCREATE TABLE teacher ( id INT(10) NOT NULL, name VARCHAR(30) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEINNODB DEFAULT CHARSETutf8 INSERT INTO teacher(id, name) VALUES (1, 秦老师); CREATE TABLE student (