Kata pengantar
Artikel ini terutama menceritakan kisah Springboot mengintegrasikan mybatis, druid dan pageHelper dan menerapkan beberapa sumber data dan pagination. Di antara mereka, Springboot mengintegrasikan mybatis, yang telah dijelaskan dalam artikel sebelumnya, jadi saya tidak akan menjelaskannya terlalu banyak di sini. Fokusnya adalah cara mengkonfigurasi druid dan pageHelper di beberapa sumber data.
Pendahuluan dan Penggunaan Druid
Sebelum menggunakan Druid, mari kita lihat secara singkat Druid.
Druid adalah kumpulan koneksi database. Druid dapat dikatakan sebagai kumpulan koneksi basis data terbaik saat ini! Ini sangat disukai oleh pengembang karena fitur, kinerja, dan skalabilitasnya yang luar biasa.
Druid telah mengerahkan lebih dari 600 aplikasi di Alibaba, dan telah melalui uji ketat penyebaran skala besar di lingkungan produksi selama lebih dari setahun. Druid adalah kumpulan koneksi basis data yang dikembangkan oleh Alibaba yang disebut pemantauan!
Pada saat yang sama, Druid bukan hanya kumpulan koneksi basis data, inti dari Druid terutama mencakup tiga bagian:
Fungsi utama druid adalah sebagai berikut:
Saya tidak akan berbicara tentang pengantar, silakan merujuk ke dokumentasi resmi untuk detailnya.
Lalu mari kita mulai memperkenalkan cara menggunakan Druid.
Pertama -tama, ketergantungan Maven, cukup tambahkan toples druid.
<dependency> <GroupId> com.alibaba </groupid> <ArTifactId> druid </artifactid> <version> 1.1.8 </version> </dependency>
Dalam hal konfigurasi, hal utama adalah menambahkannya di application.properties atau application.yml sebagai berikut.
Catatan: Karena saya menggunakan dua sumber data di sini, itu hanya sedikit berbeda. Instruksi untuk konfigurasi Druid sudah dirinci di bawah ini, jadi saya tidak akan menjelaskannya di sini.
Sumber Data Default ## master.datasource.url = jdbc: mysql: // localhost: 3306/springboot? UseUnicode = true & characterencoding = utf8 & allowmultiqueries = tru emaster.datasource.username = rootmaster.datasource.password = 123456master.datasource.driverclassname = com.mysql.jdbc.driver ## Sumber data lainnya cluster.datasource.url = jdbc: mysql: // localhost: 3306/springboot_test? Useunicode = true & characterencoding = utf8cluster.datasource.dataSerCoRCer.datasOure.password = 123456cluster.dataSource.dataRasname = 123456cluster.dataSource.driverce.datasource.password = 123456cluster.dataSource.driverce.datasource.password = 123456cluster.dataSource.dataRasnce.passne = 123456cluster.dataSource.dataSnce.password = 123456cluster.dataSource.dataSnace Informasi Konfigurasi untuk Koleksi Koneksi # Inisialisasi Ukuran, Minimum, Maksimum Spring.DataSource.Type = com.alibaba.druid.pool.druidDataSourcespring.dataSource.initialsize = 5 spring.dataSource.minidle = 5 spring.dataSource.maxactive = 20 # 20 spring.datasource.maxwait = 60000 # Mengkonfigurasi berapa lama waktu yang dibutuhkan untuk melakukan interval deteksi untuk mendeteksi koneksi idle yang perlu ditutup, dalam milidetik spring.datasource.Time -timeweenevictionrunsmillis = 60000 # Konfigurasikan waktu minimum untuk bertahan hidup di kolam renang, di millisecills. spring.datasource.validationQuery = pilih 1 dari dual spring.datasource.testwhileIdle = true spring.datasource.testonborrow = false spring.datasource.testonreturn = false # buka pscache dan tentukan ukuran pscache pada setiap koneksi spring.dataSource.poolprep spring.datasource.MaxPoolPreparedStatementPerConnectionSize = 20 # Configure filter untuk memantau statistik dicegat. Setelah menghapusnya, antarmuka pemantauan SQL tidak dapat dihitung. 'Wall' digunakan untuk firewall spring.datasource.filters = stat, wall, log4j # terbuka fungsi mergesql melalui properti connectProperties; Slow SQL Records spring.datasource.connectionProperties = druid.stat.mergesql = true; druid.stat.slowsqlmillis = 5000
Setelah berhasil menambahkan file konfigurasi, mari kita tulis kelas terkait Druid.
Pertama, kelas MasterDataSourCeconfig.java, yang merupakan kelas konfigurasi sumber data default.
@Configuration@mapperscan (basepackages = masterDataSourCeConfig.package, sqlSessionFactoryRef = "mastersqlsessionfactory") MasterDataSourConfig {static final string packer = "com.pancm.dao.master"; string final statis mapper_location = "classpath: mapper/master/*. xml"; @Value ("$ {master.datasource.url}") private string url; @Value ("$ {master.datasource.username}") Private String username; @Value ("$ {master.datasource.password}") kata sandi string pribadi; @Value ("$ {master.datasource.driverclassname}") Private String DriverClassName; @Value ("$ {spring.datasource.initialsize}") Private int Inisizasi; @Value ("$ {spring.datasource.minidle}") private int minidle; @Value ("$ {spring.datasource.maxactive}") private int maxactive; @Value ("$ {spring.datasource.maxwait}") private int maxwait; @Value ("$ {spring.datasource.TimeBetweenEvictionRunsmillis}") private int timeTeFareVictionRunsmillis; @Value ("$ {spring.datasource.minevictableIdletimeMillis}") private int minevictableIdletimemillis; @Value ("$ {spring.datasource.ValidationQuery}") Private String ValidationQuery; @Value ("$ {spring.datasource.testwhileidle}") private boolean testwhileidle; @Value ("$ {spring.datasource.testonborrow}") private boolean testonborrow; @Value ("$ {spring.datasource.testonreturn}") private boolean testonreturn; @Value ("$ {spring.datasource.poolpreparedstatements}") private boolean poolpreparedStatements; @Value ("$ {spring.datasource.maxpoolPreparedStatePerConnectionSize}") private int maxPoolPreparedStatePerConnectionSize; @Value ("$ {spring.datasource.filters}") filter string pribadi; @Value ("{Spring.Datasource.ConnectionProperties}") Private String ConnectionProperties; @Bean (name = "MasterDataSource") @primary public DataSource MasterDataSource () {druidDataSource dataSource = new druidDataSource (); DataSource.setUrl (URL); DataSource.SetUserName (nama pengguna); DataSource.setPassword (kata sandi); DataSource.SetDriverClassName (DriverClassName); // konfigurasi spesifik dataSource.setInitialSize (inisialisasi); DataSource.SetMinidle (Minidle); DataSource.SetMaxActive (MaxActive); DataSource.SetMaxwait (MaxWait); DataSource.SettimeBetweeneVictionRunsmillis (Timebetweenevictionrunsmillis); DataSource.setMinEvictableIdletimemillis (MinEvictableIdletimeMillis); DataSource.SetValidationQuery (ValidationQuery); DataSource.settestwhileIdle (testwhileidle); DataSource.settestonborrow (testonborrow); DataSource.settestonreturn (testonreturn); DataSource.SetPoolPreparedStatements (PoolPreparedStatements); DataSource.SetMaxPoolPreparedStatementPerConnectionSize (MaxPoolPreparedStatePerConnectionSize); coba {DataSource.SetFilters (filter); } catch (sqlexception e) {e.printstacktrace (); } DataSource.SetConnectionProperties (ConnectionProperties); Return DataSource; } @Bean (name = "mastertransactionManager") @primary public DataSourCetRansactionManager MasterTransActionManager () {return DataSourCetRansActionManager baru (MasterDataSource ()); } @Bean(name = "masterSqlSessionFactory") @Primary public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionfactory.setDataSource (MasterDataSource); sessionfactory.setMapperLocations (PathMatchingResourCePatternResolver baru () .getResources (MasterDataSourCeConfig.mapper_location)); return sessionfactory.getObject (); }}Kedua anotasi ini dijelaskan di bawah ini:
**@primer **: logo kacang ini jika ada beberapa kandidat kacang serupa, kacang
Prioritas dipertimbangkan. Saat mengkonfigurasi beberapa sumber data, berhati -hatilah bahwa harus ada sumber data primer dan menggunakan @primary untuk menandai kacang.
**@mapperscan **: Pindai antarmuka mapper dan manajemen kontainer.
Perlu dicatat bahwa SQLSessionFactoryRef mewakili mendefinisikan contoh unik SQLSessionFactory.
Setelah konfigurasi di atas selesai, Druid dapat digunakan sebagai kumpulan koneksi. Namun, Druid bukan sekadar kumpulan koneksi. Ini juga dapat dikatakan sebagai aplikasi pemantauan. Muncul dengan antarmuka pemantauan web, yang dapat dengan jelas melihat informasi terkait SQL.
Menggunakan fungsi pemantauan Druid di Springboot, Anda hanya perlu menulis kelas StatViewServlet dan WebStatFilter untuk mengimplementasikan layanan pendaftaran dan aturan penyaringan. Di sini kita dapat menulis keduanya bersama -sama, menggunakan **@configuration ** dan **@bean **.
Demi pemahaman yang mudah, instruksi konfigurasi yang relevan juga ditulis dalam kode, jadi saya tidak akan membahas detail di sini.
Kodenya adalah sebagai berikut:
@ConfigurationPublic Class DruIdConfiguration {@Bean Public ServeTreTregistrationBean DruidStatViewSerVle () {// Layanan Layanan ServeLegistrationBean ServletregistrationBean = new ServletregistrationBean (StatstViewServlet baru (), "/druid/*"); // WhiteList (mewakili kosong, semua dapat diakses, dipisahkan oleh koma untuk beberapa IP) servletregistrationBean.addinitparameter ("izinkan", "127.0.0.1"); // ip blacklist (dony lebih diutamakan daripada memungkinkan ketika ada keberadaan umum) servletregistrationbean.addinitparameter ("tolak", "127.0.0.2"); // atur nama pengguna login dan kata sandi servletregistrationBean.addinitparameter ("loginusername", "pancm"); servletregistrationBean.addinitparameter ("LoginPassword", "123456"); // Apakah mungkin untuk mengatur ulang data. servletregistrationBean.addinitparameter ("resetenable", "false"); return servletregistrationBean; } @Bean Public FilterRegistrationBean DruidStatFilter () {filterRegistrationBean filterRegistrationBean = new filterregistrationBean (webstatfilter baru ()); // Tambahkan aturan penyaringan filterregistrationBean.addurlpatterns ("/*"); // Tambahkan informasi format yang tidak perlu diabaikan filterregistrationBean.addinitparameter ("pengecualian", "*.js,*. Gif,*. Jpg,*. Png,*. Css,*. Ico,/druid/*"); System.out.println ("Inisialisasi Druid berhasil!"); return filterregistrationBean; }}Setelah menulis, mulailah program, masukkan: http://127.0.0.1:8084/druid/index.html di browser, dan kemudian masukkan nama pengguna dan kata sandi yang diatur untuk mengakses antarmuka web.
Konfigurasi Sumber Multi-Data
Sebelum melakukan konfigurasi sumber multi-data, jalankan skrip berikut di database MySQL dari Springboot dan Springboot_Test.
-Script dari Springboot Library Buat tabel `t_user` (` id` int (11) bukan null auto_increment komentar 'ID self-increment', `name` varchar (10) Default null Comment 'Name',` Age` Int (2) Default Null Comment 'Age', Key Primar Perpustakaan Buat tabel `t_student` (` id` int (11) bukan null auto_increment, `name` varchar (16) default null,` usia` int (11) nol default, kunci primer (`id`)) engine = innodb auto_increment = 2 charset default = utf8 (` id`)) engine = innodb auto_increment = 2 charset default = UTF8)
Catatan: Agar malas, struktur kedua tabel dibuat sama! Tapi itu tidak akan mempengaruhi tes!
Informasi tentang kedua sumber data ini telah dikonfigurasi di Application.properties, dan konfigurasi telah diposting sekali, jadi saya tidak akan mempostingnya di sini.
Di sini kita akan fokus pada konfigurasi sumber data kedua. Ini mirip dengan MasterDataSourCeconfig.java di atas, perbedaannya adalah berbeda dari anotasi dan nama **@@primer ** tanpa menggunakan anotasi **@primer **. Perlu dicatat bahwa MasterDataSourCeconfig.java memindai paket dan mapper secara akurat ke direktori, dan hal yang sama berlaku untuk sumber data kedua di sini. Maka kodenya adalah sebagai berikut:
@Configuration@mapperscan (BasEpackages = clusterDataSourCeConfig.package, sqlSessionFactoryRef = "clustersqlSessionFactory") kelas publik clusterDataSourConfig {static final string package = "com.pancmm.dao.cluster"; string final statis mapper_location = "classpath: mapper/cluster/*. xml"; @Value ("$ {cluster.datasource.url}") private string url; @Value ("$ {cluster.datasource.username}") Private String username; @Value ("$ {cluster.datasource.password}") kata sandi string pribadi; @Value ("$ {cluster.datasource.driverclassname}") Private String DriverClass; // Suka MasterDataSourCeConfig, di sini @Bean (name = "clusterDataSource") DataSource public clusterDataSource () {druidDataSource dataSource = new druidDataSource (); DataSource.setUrl (URL); DataSource.SetUserName (nama pengguna); DataSource.setPassword (kata sandi); DataSource.SetDriverClassName (DriverClass); // seperti masterdataSourCeConfig, di sini ... return dataSource; } @Bean (name = "clusterTransactionManager") public DataSourCetRansactionManager clusterTransActionManager () {return DataSourCetRansActionManager baru (clusterDataSource ()); } @Bean(name = "clusterSqlSessionFactory") public SqlSessionFactory clusterSqlSessionFactory(@Qualifier("clusterDataSource") DataSource clusterDataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionfactory.setDataSource (clusterDataSource); sessionfactory.setMapperLocations (PathMatchingResourCePatternResolver baru (). GetResources (clusterDataSourCeconfig.mapper_location)); return sessionfactory.getObject (); }} Setelah berhasil menulis konfigurasi, mulai program dan melakukan tes.
Gunakan antarmuka untuk menambahkan data di pustaka springboot dan springboot_test.
t_user
Posting http: // localhost: 8084/API/user {"name": "zhang san", "usia": 25} {"name": "li si", "usia": 25} {"name": "wang wu", "usia": 25}t_student
Posting http: // localhost: 8084/API/siswa {"name": "siswa a", "usia": 16} {"name": "siswa b", "usia": 17} {"name": "siswa c", "usia": 18}Setelah berhasil menambahkan data, hubungi antarmuka yang berbeda untuk kueri.
bertanya:
Dapatkan http: // localhost: 8084/API/user? Name = li si
kembali:
{"id": 2, "name": "li si", "usia": 25}bertanya:
Dapatkan http: // localhost: 8084/API/Siswa? Nama = Siswa c
kembali:
{"id": 1, "name": "Student C", "Age": 16}Dari data, kita dapat melihat bahwa beberapa sumber data telah berhasil dikonfigurasi.
Implementasi Pagination PageHelper
PageHelper adalah plugin paging untuk mybatis, yang sangat berguna! Sangat direkomendasikan di sini! Lai Lai
PageHelper sangat mudah digunakan, Anda hanya perlu menambahkan ketergantungan PageHelper di Maven.
Ketergantungan Maven adalah sebagai berikut:
<dependency> <GroupId> com.github.pagehelper </groupid> <ArTifactId> pageHelper-spring-boot-starter </t Artifactid> <version> 1.2.3 </version> </dependency>
Catatan: Saya menggunakan versi springboot di sini! Versi lain juga dapat digunakan.
Setelah menambahkan dependensi, Anda hanya perlu menambahkan konfigurasi atau kode berikut.
Tipe pertama ditambahkan di application.properties atau application.yml
PageHelper: helperdialect: mysql offsetaspagenum: true rowboundswithcount: true wajar: false
Tipe kedua ditambahkan dalam konfigurasi mybatis.xml
<bean id = "sqlSessionFactory"> <properti name = "DataSource" ref = "DataSource"/> <!-pindai file pemetaan.xml-> <nama properti = "mapperlocations" value = "classpath: mapper/*. xml"> </properti </properti <! <!-mengkonfigurasi plugin paging-classpath-mapper/*. xml "> </properti> <! Name = "Properties"> <value> helperdialect = mysql offsetaspagenum = true rowboundswithcount = true wajar = false </ value> </preate> </bean> </array> </properti> </bean>
Tipe ketiga ditambahkan dalam kode dan menginisialisasi saat memulai program menggunakan anotasi **@bean **.
@Bean Public PageHelper pageHelper () {pageHelper pageHelper = new pageHelper (); Properti properti = properti baru (); // properti basis data.setProperty ("helperdialect", "mysql"); // apakah akan menggunakan parameter offset sebagai properti pagenum.setProperty ("offsetaspagenum", "true"); // apakah akan menanyakan penghitungan properti.setProperty ("rowboundswithcount", "true"); // apakah akan merasionalisasi properti pagination.setProperty ("wajar", "false"); pageHelper.setProperties (properti); }Karena kami menggunakan beberapa sumber data di sini, konfigurasi di sini sedikit berbeda. Kita perlu mengonfigurasinya di sessionFactory. Di sini kami membuat modifikasi yang sesuai untuk MasterDataSourCeconfig.java. Dalam metode MastersQLSessionFactory, tambahkan kode berikut.
@Bean(name = "masterSqlSessionFactory") @Primary public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionfactory.setDataSource (MasterDataSource); sessionfactory.setMapperLocations (PathMatchingResourCePatternResolver baru () .getResources (MasterDataSourCeConfig.mapper_location)); // Pagination Plug-in Interceptor Interceptor = New PageInterceptor (); Properti properti = properti baru (); // properti basis data.setProperty ("helperdialect", "mysql"); // apakah akan menggunakan parameter offset sebagai properti pagenum.setProperty ("offsetaspagenum", "true"); // apakah akan menanyakan penghitungan properti.setProperty ("rowboundswithcount", "true"); // apakah akan merasionalisasi properti pagination.setProperty ("wajar", "false"); interceptor.setProperties (properti); sessionfactory.setPlugins (interceptor baru [] {interceptor}); return sessionfactory.getObject (); }Catatan: Ketika sumber data lain juga ingin paging, silakan merujuk ke kode di atas.
Apa yang perlu Anda perhatikan di sini adalah parameter yang masuk akal, yang berarti rasionalisasi paging, dan nilai defaultnya salah. Jika parameter ini diatur ke True, halaman pertama akan diminta ketika Pagenum <= 0, dan halaman Pagenum> (ketika jumlah total melebihi), halaman terakhir akan diminta. Ketika false secara default, kueri langsung didasarkan pada parameter.
Setelah pengaturan PageHelper, jika Anda menggunakannya, Anda hanya perlu menambahkan PageHelper.startPage(pageNum,pageSize); Di depan kueri SQL. Jika Anda ingin mengetahui jumlah total, beli setelah pernyataan SQL kueri dan tambahkan page.getTotal() .
Contoh kode:
Daftar Publik <T> findByListentity (t entitas) {Daftar <T> Daftar = null; coba {page <?> halaman = pageHelper.startpage (1,2); System.out.println (getClassName (entitas)+"Setel dua data di halaman pertama!"); list = getMapper (). findBylistentity (entitas); System.out.println ("Ada total:"+page.getTotal ()+"data, dan pengembalian aktual adalah:"+list.size ()+"Dua data!"); } catch (Exception e) {Logger.Error ("Query"+getClassName (entitas)+"Gagal! Alasannya adalah:", e); } daftar pengembalian; }Setelah kode ditulis, tes akhir dimulai.
Permintaan semua data dalam tabel T_USER dan menyuruhnya.
bertanya:
Dapatkan http: // localhost: 8084/API/pengguna
kembali:
[{"id": 1, "name": "zhang san", "usia": 25}, {"id": 2, "name": "li si", "usia": 25}]Cetak konsol:
Mulailah menanyakan ...
Pengguna menetapkan dua potong data di halaman pertama!
2018-04-27 19: 55: 50.769 DEBUG 6152 --- [IO-8084-EXEC-10] CPDMUSERDAO.FindbyListentity_Count: ==> Mempersiapkan: Pilih Hitung (0) dari T_USER di mana 1 = 1
2018-04-27 19: 55: 50.770 DEBUG 6152 --- [IO-8084-EXEC-10] CPDMUSERDAO.FindByListentity_Count: ==> Parameter:
2018-04-27 19: 55: 50.771 DEBUG 6152 --- [IO-8084-EXEC-10] CPDMUSERDAO.FindByListentity_Count: <== Total: 1
2018-04-27 19: 55: 50.772 DEBUG 6152 --- [IO-8084-EXEC-10] cpdao.master.userdao.findbylistentity: ==> Mempersiapkan: Pilih id, nama, usia dari T_USER di mana 1 = 1 batas?
2018-04-27 19: 55: 50.773 DEBUG 6152 --- [IO-8084-EXEC-10] cpdao.master.userdao.findbylistentity: ==> Parameter: 2 (integer)
2018-04-27 19: 55: 50.774 DEBUG 6152 --- [IO-8084-EXEC-10] cpdao.master.userdao.findbylistentity: <== Total: 2
Ada total: 3 lembar data, dan pengembalian aktual adalah: 2 dua potong data!
Permintaan semua data dalam tabel T_Student dan paginasinya.
bertanya:
Dapatkan http: // localhost: 8084/API/siswa
kembali:
[{"id": 1, "name": "siswa a", "usia": 16}, {"id": 2, "name": "siswa b", "usia": 17}]Cetak konsol:
Mulailah menanyakan ...
Studnet menetapkan dua potong data di halaman pertama!
2018-04-27 19: 54: 56.155 DEBUG 6152 --- [NIO-8084-EXEC-8] CPDCSFINDBYLISTITY_COUNT: ==> Mempersiapkan: Pilih Hitung (0) Dari T_Student di mana 1 = 1
2018-04-27 19: 54: 56.155 DEBUG 6152 --- [NIO-8084-EXEC-8] CPDCSFINDBYLISTITY_COUNT: ==> Parameter:
2018-04-27 19: 54: 56.156 DEBUG 6152 --- [NIO-8084-EXEC-8] CPDCSFINDBYLISTITY_COUNT: <== Total: 1
2018-04-27 19: 54: 56.157 DEBUG 6152 --- [NIO-8084-EXEC-8] CPDCSTUDENTDAO.FindbyListentity: ==> Mempersiapkan: Pilih ID, Nama, Usia dari T_Student di mana 1 = 1 batas?
2018-04-27 19: 54: 56.157 DEBUG 6152 --- [NIO-8084-EXEC-8] CPDCSTUDENTDAO.FindByListentity: ==> Parameter: 2 (Integer)
2018-04-27 19: 54: 56.157 DEBUG 6152 --- [NIO-8084-EXEC-8] CPDCSTUDENTDAO.FindByListentity: <== Total: 2
Ada total: 3 lembar data, dan pengembalian aktual adalah: 2 dua potong data!
Setelah kueri selesai, mari kita lihat antarmuka pemantauan Druid. Masukkan di browser: http://127.0.0.1:8084/druid/index.html
Catatan operasi dapat dilihat dengan jelas!
Jika Anda ingin tahu lebih banyak tentang Druid, Anda dapat memeriksa dokumentasi resmi!
Kesimpulan
Artikel ini akhirnya selesai. Ketika menulis kode, saya mengalami banyak masalah, dan kemudian saya perlahan mencoba dan menemukan informasi untuk menyelesaikannya. Artikel ini hanya memperkenalkan penggunaan terkait ini dengan sangat singkat, dan mungkin lebih rumit dalam aplikasi yang sebenarnya.
Artikel referensi: https://www.bysocket.com/?p=1712
Alamat resmi Durid: https://github.com/alibaba/druid
Alamat resmi PageHelper: https://github.com/pagehelper/mybatis-pagehelper
Saya menempatkan proyek di github: https://github.com/xuwujing/springboot, Anda juga dapat mengunduhnya secara lokal: klik di sini
Meringkaskan
Di atas adalah seluruh konten artikel ini. Saya berharap konten artikel ini memiliki nilai referensi tertentu untuk studi atau pekerjaan semua orang. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi. Terima kasih atas dukungan Anda ke wulin.com.